From ca6a73c3be915f67d2365450856219f73e28a53e Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 02:19:26 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E7=B5=B1?= =?UTF-8?q?=E4=B8=80=E9=8C=AF=E8=AA=A4=E8=99=95=E7=90=86=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E8=88=87=E8=B3=87=E6=BA=90=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E5=84=AA=E5=8C=96=E8=87=A8=E6=99=82=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E8=88=87=E7=AB=AF=E5=8F=A3=E6=9F=A5=E6=89=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=8F=90=E5=8D=87=E7=B3=BB=E7=B5=B1=E7=A9=A9?= =?UTF-8?q?=E5=AE=9A=E6=80=A7=E8=88=87=E5=8F=AF=E7=B6=AD=E8=AD=B7=E6=80=A7?= =?UTF-8?q?=E3=80=82=E6=9B=B4=E6=96=B0=20Web=20UI=20=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=82=8F=E8=BC=AF=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E6=B8=85=E7=90=86=E8=88=87=E9=80=B2=E7=A8=8B?= =?UTF-8?q?=E8=A8=BB=E5=86=8A=EF=BC=8C=E4=B8=A6=E5=A2=9E=E5=BC=B7=E5=A3=93?= =?UTF-8?q?=E7=B8=AE=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E3=80=82=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=B8=AC=E8=A9=A6=E6=A8=A1=E7=B5=84=E4=BB=A5=E7=A2=BA?= =?UTF-8?q?=E4=BF=9D=E5=8A=9F=E8=83=BD=E6=AD=A3=E7=A2=BA=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/locales/en/translations.json | 41 +- .../gui/locales/zh-CN/translations.json | 41 +- .../gui/locales/zh-TW/translations.json | 41 +- .../gui/widgets/image_upload.py | 19 +- src/mcp_feedback_enhanced/server.py | 65 +- src/mcp_feedback_enhanced/test_web_ui.py | 16 +- src/mcp_feedback_enhanced/testing/utils.py | 29 +- src/mcp_feedback_enhanced/utils/__init__.py | 27 + .../utils/error_handler.py | 455 +++++++++++ .../utils/resource_manager.py | 719 ++++++++++++++++++ src/mcp_feedback_enhanced/web/main.py | 95 ++- .../web/models/feedback_session.py | 16 +- .../web/utils/compression_config.py | 187 +++++ .../web/utils/compression_monitor.py | 290 +++++++ .../web/utils/port_manager.py | 307 ++++++++ tests/test_error_handler.py | 253 ++++++ tests/test_gzip_compression.py | 346 +++++++++ tests/test_port_manager.py | 249 ++++++ tests/test_resource_manager.py | 394 ++++++++++ 19 files changed, 3543 insertions(+), 47 deletions(-) create mode 100644 src/mcp_feedback_enhanced/utils/__init__.py create mode 100644 src/mcp_feedback_enhanced/utils/error_handler.py create mode 100644 src/mcp_feedback_enhanced/utils/resource_manager.py create mode 100644 src/mcp_feedback_enhanced/web/utils/compression_config.py create mode 100644 src/mcp_feedback_enhanced/web/utils/compression_monitor.py create mode 100644 src/mcp_feedback_enhanced/web/utils/port_manager.py create mode 100644 tests/test_error_handler.py create mode 100644 tests/test_gzip_compression.py create mode 100644 tests/test_port_manager.py create mode 100644 tests/test_resource_manager.py diff --git a/src/mcp_feedback_enhanced/gui/locales/en/translations.json b/src/mcp_feedback_enhanced/gui/locales/en/translations.json index a1e8819..ebde0b5 100644 --- a/src/mcp_feedback_enhanced/gui/locales/en/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/en/translations.json @@ -192,7 +192,46 @@ "confirmClearAll": "Are you sure you want to clear all {count} images?", "confirmClearTitle": "Confirm Clear", "fileSizeExceeded": "Image {filename} size is {size}MB, exceeding 1MB limit!\nRecommend using image editing software to compress before uploading.", - "dataSizeExceeded": "Image {filename} data size exceeds 1MB limit!" + "dataSizeExceeded": "Image {filename} data size exceeds 1MB limit!", + "types": { + "network": "Network connection issue", + "file_io": "File read/write issue", + "process": "Process execution issue", + "timeout": "Operation timeout", + "user_cancel": "User cancelled the operation", + "system": "System issue", + "permission": "Insufficient permissions", + "validation": "Data validation failed", + "dependency": "Dependency issue", + "config": "Configuration issue" + }, + "solutions": { + "network": [ + "Check network connection", + "Verify firewall settings", + "Try restarting the application" + ], + "file_io": [ + "Check if file exists", + "Verify file permissions", + "Check available disk space" + ], + "process": [ + "Check if process is running", + "Verify system resources", + "Try restarting related services" + ], + "timeout": [ + "Increase timeout settings", + "Check network latency", + "Retry the operation later" + ], + "permission": [ + "Run as administrator", + "Check file/directory permissions", + "Contact system administrator" + ] + } }, "languageSelector": "🌐 Language", "languageNames": { diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json index 2f30086..f415dd9 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json @@ -172,7 +172,46 @@ "confirmClearAll": "确定要清除所有 {count} 张图片吗?", "confirmClearTitle": "确认清除", "fileSizeExceeded": "图片 {filename} 大小为 {size}MB,超过 1MB 限制!\n建议使用图片编辑软件压缩后再上传。", - "dataSizeExceeded": "图片 {filename} 数据大小超过 1MB 限制!" + "dataSizeExceeded": "图片 {filename} 数据大小超过 1MB 限制!", + "types": { + "network": "网络连接出现问题", + "file_io": "文件读写出现问题", + "process": "进程执行出现问题", + "timeout": "操作超时", + "user_cancel": "用户取消了操作", + "system": "系统出现问题", + "permission": "权限不足", + "validation": "数据验证失败", + "dependency": "依赖组件出现问题", + "config": "配置出现问题" + }, + "solutions": { + "network": [ + "检查网络连接是否正常", + "确认防火墙设置", + "尝试重新启动应用程序" + ], + "file_io": [ + "检查文件是否存在", + "确认文件权限", + "检查磁盘空间是否足够" + ], + "process": [ + "检查进程是否正在运行", + "确认系统资源是否足够", + "尝试重新启动相关服务" + ], + "timeout": [ + "增加超时时间设置", + "检查网络延迟", + "稍后重试操作" + ], + "permission": [ + "以管理员身份运行", + "检查文件/目录权限", + "联系系统管理员" + ] + } }, "aiSummary": "AI 工作摘要", "languageSelector": "🌐 语言选择", diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json index d134d75..80dc117 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json @@ -188,7 +188,46 @@ "confirmClearAll": "確定要清除所有 {count} 張圖片嗎?", "confirmClearTitle": "確認清除", "fileSizeExceeded": "圖片 {filename} 大小為 {size}MB,超過 1MB 限制!\n建議使用圖片編輯軟體壓縮後再上傳。", - "dataSizeExceeded": "圖片 {filename} 數據大小超過 1MB 限制!" + "dataSizeExceeded": "圖片 {filename} 數據大小超過 1MB 限制!", + "types": { + "network": "網絡連接出現問題", + "file_io": "文件讀寫出現問題", + "process": "進程執行出現問題", + "timeout": "操作超時", + "user_cancel": "用戶取消了操作", + "system": "系統出現問題", + "permission": "權限不足", + "validation": "數據驗證失敗", + "dependency": "依賴組件出現問題", + "config": "配置出現問題" + }, + "solutions": { + "network": [ + "檢查網絡連接是否正常", + "確認防火牆設置", + "嘗試重新啟動應用程序" + ], + "file_io": [ + "檢查文件是否存在", + "確認文件權限", + "檢查磁盤空間是否足夠" + ], + "process": [ + "檢查進程是否正在運行", + "確認系統資源是否足夠", + "嘗試重新啟動相關服務" + ], + "timeout": [ + "增加超時時間設置", + "檢查網絡延遲", + "稍後重試操作" + ], + "permission": [ + "以管理員身份運行", + "檢查文件/目錄權限", + "聯繫系統管理員" + ] + } }, "languageNames": { "zhTw": "繁體中文", diff --git a/src/mcp_feedback_enhanced/gui/widgets/image_upload.py b/src/mcp_feedback_enhanced/gui/widgets/image_upload.py index f2e79bf..88332a1 100644 --- a/src/mcp_feedback_enhanced/gui/widgets/image_upload.py +++ b/src/mcp_feedback_enhanced/gui/widgets/image_upload.py @@ -25,6 +25,7 @@ from PySide6.QtWidgets import QSizePolicy # 導入多語系支援 from ...i18n import t from ...debug import gui_debug_log as debug_log +from ...utils.resource_manager import get_resource_manager, create_temp_file from .image_preview import ImagePreviewWidget @@ -37,6 +38,7 @@ class ImageUploadWidget(QWidget): self.images: Dict[str, Dict[str, str]] = {} self.config_manager = config_manager self._last_paste_time = 0 # 添加最後貼上時間記錄 + self.resource_manager = get_resource_manager() # 獲取資源管理器 self._setup_ui() self.setAcceptDrops(True) # 啟動時清理舊的臨時文件 @@ -350,20 +352,19 @@ class ImageUploadWidget(QWidget): if mimeData.hasImage(): image = clipboard.image() if not image.isNull(): - # 創建一個唯一的臨時文件名 - temp_dir = Path.home() / ".cache" / "mcp-feedback-enhanced" - temp_dir.mkdir(parents=True, exist_ok=True) - - timestamp = int(time.time() * 1000) - temp_file = temp_dir / f"clipboard_{timestamp}_{uuid.uuid4().hex[:8]}.png" + # 使用資源管理器創建臨時文件 + temp_file = create_temp_file( + suffix=".png", + prefix=f"clipboard_{int(time.time() * 1000)}_" + ) # 保存剪貼板圖片 - if image.save(str(temp_file), "PNG"): + if image.save(temp_file, "PNG"): if os.path.getsize(temp_file) > 0: - self._add_images([str(temp_file)]) + self._add_images([temp_file]) debug_log(f"從剪貼板成功粘貼圖片: {temp_file}") else: - QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveEmpty', path=str(temp_file))) + QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveEmpty', path=temp_file)) else: QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveFailed')) else: diff --git a/src/mcp_feedback_enhanced/server.py b/src/mcp_feedback_enhanced/server.py index c0002e0..c1d5606 100644 --- a/src/mcp_feedback_enhanced/server.py +++ b/src/mcp_feedback_enhanced/server.py @@ -38,6 +38,12 @@ from .i18n import get_i18n_manager # 導入統一的調試功能 from .debug import server_debug_log as debug_log +# 導入錯誤處理框架 +from .utils.error_handler import ErrorHandler, ErrorType + +# 導入資源管理器 +from .utils.resource_manager import get_resource_manager, create_temp_file + # ===== 編碼初始化 ===== def init_encoding(): """初始化編碼設置,確保正確處理中文字符""" @@ -228,8 +234,8 @@ def save_feedback_to_file(feedback_data: dict, file_path: str = None) -> str: str: 儲存的文件路徑 """ if file_path is None: - temp_fd, file_path = tempfile.mkstemp(suffix='.json', prefix='feedback_') - os.close(temp_fd) + # 使用資源管理器創建臨時文件 + file_path = create_temp_file(suffix='.json', prefix='feedback_') # 確保目錄存在 directory = os.path.dirname(file_path) @@ -401,9 +407,13 @@ def process_images(images_data: List[dict]) -> List[MCPImage]: debug_log(f"圖片 {i} ({file_name}) 處理成功,格式: {image_format}") except Exception as e: - debug_log(f"圖片 {i} 處理失敗: {e}") - import traceback - debug_log(f"詳細錯誤: {traceback.format_exc()}") + # 使用統一錯誤處理(不影響 JSON RPC) + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "圖片處理", "image_index": i}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"圖片 {i} 處理失敗 [錯誤ID: {error_id}]: {e}") debug_log(f"共處理 {len(mcp_images)} 張圖片") return mcp_images @@ -539,9 +549,18 @@ async def interactive_feedback( return feedback_items except Exception as e: - error_msg = f"回饋收集錯誤: {str(e)}" - debug_log(f"錯誤: {error_msg}") - return [TextContent(type="text", text=error_msg)] + # 使用統一錯誤處理,但不影響 JSON RPC 響應 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "回饋收集", "project_dir": project_dir}, + error_type=ErrorType.SYSTEM + ) + + # 生成用戶友好的錯誤信息 + user_error_msg = ErrorHandler.format_user_error(e, include_technical=False) + debug_log(f"回饋收集錯誤 [錯誤ID: {error_id}]: {str(e)}") + + return [TextContent(type="text", text=user_error_msg)] async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: int) -> dict: @@ -565,10 +584,18 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in # 傳遞 timeout 參數給 Web UI return await launch_web_feedback_ui(project_dir, summary, timeout) except ImportError as e: - debug_log(f"無法導入 Web UI 模組: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "Web UI 模組導入", "module": "web"}, + error_type=ErrorType.DEPENDENCY + ) + user_error_msg = ErrorHandler.format_user_error(e, ErrorType.DEPENDENCY, include_technical=False) + debug_log(f"Web UI 模組導入失敗 [錯誤ID: {error_id}]: {e}") + return { "command_logs": "", - "interactive_feedback": f"Web UI 模組導入失敗: {str(e)}", + "interactive_feedback": user_error_msg, "images": [] } except TimeoutError as e: @@ -587,19 +614,31 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in "images": [] } except Exception as e: - error_msg = f"Web UI 錯誤: {e}" - debug_log(f"❌ {error_msg}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "Web UI 啟動", "timeout": timeout}, + error_type=ErrorType.SYSTEM + ) + user_error_msg = ErrorHandler.format_user_error(e, include_technical=False) + debug_log(f"❌ Web UI 錯誤 [錯誤ID: {error_id}]: {e}") + # 發生錯誤時也要停止 Web 服務器 try: from .web import stop_web_ui stop_web_ui() debug_log("Web UI 服務器已因錯誤而停止") except Exception as stop_error: + ErrorHandler.log_error_with_context( + stop_error, + context={"operation": "Web UI 服務器停止"}, + error_type=ErrorType.SYSTEM + ) debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}") return { "command_logs": "", - "interactive_feedback": f"錯誤: {str(e)}", + "interactive_feedback": user_error_msg, "images": [] } diff --git a/src/mcp_feedback_enhanced/test_web_ui.py b/src/mcp_feedback_enhanced/test_web_ui.py index 863df70..3b8740f 100644 --- a/src/mcp_feedback_enhanced/test_web_ui.py +++ b/src/mcp_feedback_enhanced/test_web_ui.py @@ -95,11 +95,17 @@ def get_test_summary(): def find_free_port(): """Find a free port to use for testing""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - s.listen(1) - port = s.getsockname()[1] - return port + try: + # 嘗試使用增強的端口管理 + from .web.utils.port_manager import PortManager + return PortManager.find_free_port_enhanced(preferred_port=8765, auto_cleanup=False) + except ImportError: + # 回退到原始方法 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port def test_web_ui(keep_running=False): """Test the Web UI functionality""" diff --git a/src/mcp_feedback_enhanced/testing/utils.py b/src/mcp_feedback_enhanced/testing/utils.py index 114bf50..7c62ed1 100644 --- a/src/mcp_feedback_enhanced/testing/utils.py +++ b/src/mcp_feedback_enhanced/testing/utils.py @@ -54,15 +54,26 @@ class TestUtils: @staticmethod def find_free_port(start_port: int = 8765, max_attempts: int = 100) -> int: - """尋找可用端口""" - for port in range(start_port, start_port + max_attempts): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('127.0.0.1', port)) - return port - except OSError: - continue - raise RuntimeError(f"無法找到可用端口 (嘗試範圍: {start_port}-{start_port + max_attempts})") + """尋找可用端口 - 使用增強的端口管理""" + try: + # 嘗試使用增強的端口管理 + from ..web.utils.port_manager import PortManager + return PortManager.find_free_port_enhanced( + preferred_port=start_port, + auto_cleanup=False, # 測試時不自動清理 + host='127.0.0.1', + max_attempts=max_attempts + ) + except ImportError: + # 回退到原始方法 + for port in range(start_port, start_port + max_attempts): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', port)) + return port + except OSError: + continue + raise RuntimeError(f"無法找到可用端口 (嘗試範圍: {start_port}-{start_port + max_attempts})") @staticmethod def is_port_open(host: str, port: int, timeout: float = 1.0) -> bool: diff --git a/src/mcp_feedback_enhanced/utils/__init__.py b/src/mcp_feedback_enhanced/utils/__init__.py new file mode 100644 index 0000000..2e2001e --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/__init__.py @@ -0,0 +1,27 @@ +""" +MCP Feedback Enhanced 工具模組 +============================ + +提供各種工具類和函數,包括錯誤處理、資源管理等。 +""" + +from .error_handler import ErrorHandler, ErrorType +from .resource_manager import ( + ResourceManager, + get_resource_manager, + create_temp_file, + create_temp_dir, + register_process, + cleanup_all_resources +) + +__all__ = [ + 'ErrorHandler', + 'ErrorType', + 'ResourceManager', + 'get_resource_manager', + 'create_temp_file', + 'create_temp_dir', + 'register_process', + 'cleanup_all_resources' +] diff --git a/src/mcp_feedback_enhanced/utils/error_handler.py b/src/mcp_feedback_enhanced/utils/error_handler.py new file mode 100644 index 0000000..dd6a867 --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/error_handler.py @@ -0,0 +1,455 @@ +""" +統一錯誤處理框架 +================ + +提供統一的錯誤處理機制,包括: +- 錯誤類型分類 +- 用戶友好錯誤信息 +- 錯誤上下文記錄 +- 解決方案建議 +- 國際化支持 + +注意:此模組不會影響 JSON RPC 通信,所有錯誤處理都在應用層進行。 +""" + +import os +import sys +import traceback +import time +from enum import Enum +from typing import Dict, Any, Optional, List, Tuple +from ..debug import debug_log + + +class ErrorType(Enum): + """錯誤類型枚舉""" + NETWORK = "network" # 網絡相關錯誤 + FILE_IO = "file_io" # 文件 I/O 錯誤 + PROCESS = "process" # 進程相關錯誤 + TIMEOUT = "timeout" # 超時錯誤 + USER_CANCEL = "user_cancel" # 用戶取消操作 + SYSTEM = "system" # 系統錯誤 + PERMISSION = "permission" # 權限錯誤 + VALIDATION = "validation" # 數據驗證錯誤 + DEPENDENCY = "dependency" # 依賴錯誤 + CONFIGURATION = "config" # 配置錯誤 + + +class ErrorSeverity(Enum): + """錯誤嚴重程度""" + LOW = "low" # 低:不影響核心功能 + MEDIUM = "medium" # 中:影響部分功能 + HIGH = "high" # 高:影響核心功能 + CRITICAL = "critical" # 嚴重:系統無法正常運行 + + +class ErrorHandler: + """統一錯誤處理器""" + + # 錯誤類型到用戶友好信息的映射 + _ERROR_MESSAGES = { + ErrorType.NETWORK: { + "zh-TW": "網絡連接出現問題", + "zh-CN": "网络连接出现问题", + "en": "Network connection issue" + }, + ErrorType.FILE_IO: { + "zh-TW": "文件讀寫出現問題", + "zh-CN": "文件读写出现问题", + "en": "File read/write issue" + }, + ErrorType.PROCESS: { + "zh-TW": "進程執行出現問題", + "zh-CN": "进程执行出现问题", + "en": "Process execution issue" + }, + ErrorType.TIMEOUT: { + "zh-TW": "操作超時", + "zh-CN": "操作超时", + "en": "Operation timeout" + }, + ErrorType.USER_CANCEL: { + "zh-TW": "用戶取消了操作", + "zh-CN": "用户取消了操作", + "en": "User cancelled the operation" + }, + ErrorType.SYSTEM: { + "zh-TW": "系統出現問題", + "zh-CN": "系统出现问题", + "en": "System issue" + }, + ErrorType.PERMISSION: { + "zh-TW": "權限不足", + "zh-CN": "权限不足", + "en": "Insufficient permissions" + }, + ErrorType.VALIDATION: { + "zh-TW": "數據驗證失敗", + "zh-CN": "数据验证失败", + "en": "Data validation failed" + }, + ErrorType.DEPENDENCY: { + "zh-TW": "依賴組件出現問題", + "zh-CN": "依赖组件出现问题", + "en": "Dependency issue" + }, + ErrorType.CONFIGURATION: { + "zh-TW": "配置出現問題", + "zh-CN": "配置出现问题", + "en": "Configuration issue" + } + } + + # 錯誤解決建議 + _ERROR_SOLUTIONS = { + ErrorType.NETWORK: { + "zh-TW": [ + "檢查網絡連接是否正常", + "確認防火牆設置", + "嘗試重新啟動應用程序" + ], + "zh-CN": [ + "检查网络连接是否正常", + "确认防火墙设置", + "尝试重新启动应用程序" + ], + "en": [ + "Check network connection", + "Verify firewall settings", + "Try restarting the application" + ] + }, + ErrorType.FILE_IO: { + "zh-TW": [ + "檢查文件是否存在", + "確認文件權限", + "檢查磁盤空間是否足夠" + ], + "zh-CN": [ + "检查文件是否存在", + "确认文件权限", + "检查磁盘空间是否足够" + ], + "en": [ + "Check if file exists", + "Verify file permissions", + "Check available disk space" + ] + }, + ErrorType.PROCESS: { + "zh-TW": [ + "檢查進程是否正在運行", + "確認系統資源是否足夠", + "嘗試重新啟動相關服務" + ], + "zh-CN": [ + "检查进程是否正在运行", + "确认系统资源是否足够", + "尝试重新启动相关服务" + ], + "en": [ + "Check if process is running", + "Verify system resources", + "Try restarting related services" + ] + }, + ErrorType.TIMEOUT: { + "zh-TW": [ + "增加超時時間設置", + "檢查網絡延遲", + "稍後重試操作" + ], + "zh-CN": [ + "增加超时时间设置", + "检查网络延迟", + "稍后重试操作" + ], + "en": [ + "Increase timeout settings", + "Check network latency", + "Retry the operation later" + ] + }, + ErrorType.PERMISSION: { + "zh-TW": [ + "以管理員身份運行", + "檢查文件/目錄權限", + "聯繫系統管理員" + ], + "zh-CN": [ + "以管理员身份运行", + "检查文件/目录权限", + "联系系统管理员" + ], + "en": [ + "Run as administrator", + "Check file/directory permissions", + "Contact system administrator" + ] + } + } + + @staticmethod + def get_current_language() -> str: + """獲取當前語言設置""" + try: + # 嘗試從 i18n 模組獲取當前語言 + from ..i18n import get_i18n_manager + return get_i18n_manager().get_current_language() + except Exception: + # 回退到環境變數或默認語言 + return os.getenv("MCP_LANGUAGE", "zh-TW") + + @staticmethod + def get_i18n_error_message(error_type: ErrorType) -> str: + """從國際化系統獲取錯誤信息""" + try: + from ..i18n import get_i18n_manager + i18n = get_i18n_manager() + key = f"errors.types.{error_type.value}" + message = i18n.t(key) + # 如果返回的是鍵本身,說明沒有找到翻譯,使用回退 + if message == key: + raise Exception("Translation not found") + return message + except Exception: + # 回退到內建映射 + language = ErrorHandler.get_current_language() + error_messages = ErrorHandler._ERROR_MESSAGES.get(error_type, {}) + return error_messages.get(language, error_messages.get("zh-TW", "發生未知錯誤")) + + @staticmethod + def get_i18n_error_solutions(error_type: ErrorType) -> List[str]: + """從國際化系統獲取錯誤解決方案""" + try: + from ..i18n import get_i18n_manager + i18n = get_i18n_manager() + key = f"errors.solutions.{error_type.value}" + solutions = i18n.t(key) + if isinstance(solutions, list) and len(solutions) > 0: + return solutions + # 如果沒有找到或為空,使用回退 + raise Exception("Solutions not found") + except Exception: + # 回退到內建映射 + language = ErrorHandler.get_current_language() + solutions = ErrorHandler._ERROR_SOLUTIONS.get(error_type, {}) + return solutions.get(language, solutions.get("zh-TW", [])) + + @staticmethod + def classify_error(error: Exception) -> ErrorType: + """ + 根據異常類型自動分類錯誤 + + Args: + error: Python 異常對象 + + Returns: + ErrorType: 錯誤類型 + """ + error_name = type(error).__name__ + error_message = str(error).lower() + + # 超時錯誤(優先檢查,避免被網絡錯誤覆蓋) + if 'timeout' in error_name.lower() or 'timeout' in error_message: + return ErrorType.TIMEOUT + + # 權限錯誤(優先檢查,避免被文件錯誤覆蓋) + if 'permission' in error_name.lower(): + return ErrorType.PERMISSION + if any(keyword in error_message for keyword in ['permission denied', 'access denied', 'forbidden']): + return ErrorType.PERMISSION + + # 網絡相關錯誤 + if any(keyword in error_name.lower() for keyword in ['connection', 'network', 'socket']): + return ErrorType.NETWORK + if any(keyword in error_message for keyword in ['connection', 'network', 'socket']): + return ErrorType.NETWORK + + # 文件 I/O 錯誤 + if any(keyword in error_name.lower() for keyword in ['file', 'ioerror']): # 使用更精確的匹配 + return ErrorType.FILE_IO + if any(keyword in error_message for keyword in ['file', 'directory', 'no such file']): + return ErrorType.FILE_IO + + # 進程相關錯誤 + if any(keyword in error_name.lower() for keyword in ['process', 'subprocess']): + return ErrorType.PROCESS + if any(keyword in error_message for keyword in ['process', 'command', 'executable']): + return ErrorType.PROCESS + + # 驗證錯誤 + if any(keyword in error_name.lower() for keyword in ['validation', 'value', 'type']): + return ErrorType.VALIDATION + + # 配置錯誤 + if any(keyword in error_message for keyword in ['config', 'setting', 'environment']): + return ErrorType.CONFIGURATION + + # 默認為系統錯誤 + return ErrorType.SYSTEM + + @staticmethod + def format_user_error( + error: Exception, + error_type: Optional[ErrorType] = None, + context: Optional[Dict[str, Any]] = None, + include_technical: bool = False + ) -> str: + """ + 將技術錯誤轉換為用戶友好的錯誤信息 + + Args: + error: Python 異常對象 + error_type: 錯誤類型(可選,會自動分類) + context: 錯誤上下文信息 + include_technical: 是否包含技術細節 + + Returns: + str: 用戶友好的錯誤信息 + """ + # 自動分類錯誤類型 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 獲取當前語言 + language = ErrorHandler.get_current_language() + + # 獲取用戶友好的錯誤信息(優先使用國際化系統) + user_message = ErrorHandler.get_i18n_error_message(error_type) + + # 構建完整的錯誤信息 + parts = [f"❌ {user_message}"] + + # 添加上下文信息 + if context: + if context.get("operation"): + if language == "en": + parts.append(f"Operation: {context['operation']}") + else: + parts.append(f"操作:{context['operation']}") + + if context.get("file_path"): + if language == "en": + parts.append(f"File: {context['file_path']}") + else: + parts.append(f"文件:{context['file_path']}") + + # 添加技術細節(如果需要) + if include_technical: + if language == "en": + parts.append(f"Technical details: {type(error).__name__}: {str(error)}") + else: + parts.append(f"技術細節:{type(error).__name__}: {str(error)}") + + return "\n".join(parts) + + @staticmethod + def get_error_solutions(error_type: ErrorType) -> List[str]: + """ + 獲取錯誤解決建議 + + Args: + error_type: 錯誤類型 + + Returns: + List[str]: 解決建議列表 + """ + return ErrorHandler.get_i18n_error_solutions(error_type) + + @staticmethod + def log_error_with_context( + error: Exception, + context: Optional[Dict[str, Any]] = None, + error_type: Optional[ErrorType] = None, + severity: ErrorSeverity = ErrorSeverity.MEDIUM + ) -> str: + """ + 記錄帶上下文的錯誤信息(不影響 JSON RPC) + + Args: + error: Python 異常對象 + context: 錯誤上下文信息 + error_type: 錯誤類型 + severity: 錯誤嚴重程度 + + Returns: + str: 錯誤 ID,用於追蹤 + """ + # 生成錯誤 ID + error_id = f"ERR_{int(time.time())}_{id(error) % 10000}" + + # 自動分類錯誤 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 構建錯誤記錄 + error_record = { + "error_id": error_id, + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), + "error_type": error_type.value, + "severity": severity.value, + "exception_type": type(error).__name__, + "exception_message": str(error), + "context": context or {}, + "traceback": traceback.format_exc() if severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL] else None + } + + # 記錄到調試日誌(不影響 JSON RPC) + debug_log(f"錯誤記錄 [{error_id}]: {error_type.value} - {str(error)}") + + if context: + debug_log(f"錯誤上下文 [{error_id}]: {context}") + + # 對於嚴重錯誤,記錄完整堆棧跟蹤 + if severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]: + debug_log(f"錯誤堆棧 [{error_id}]:\n{traceback.format_exc()}") + + return error_id + + @staticmethod + def create_error_response( + error: Exception, + context: Optional[Dict[str, Any]] = None, + error_type: Optional[ErrorType] = None, + include_solutions: bool = True, + for_user: bool = True + ) -> Dict[str, Any]: + """ + 創建標準化的錯誤響應 + + Args: + error: Python 異常對象 + context: 錯誤上下文 + error_type: 錯誤類型 + include_solutions: 是否包含解決建議 + for_user: 是否為用戶界面使用 + + Returns: + Dict[str, Any]: 標準化錯誤響應 + """ + # 自動分類錯誤 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 記錄錯誤 + error_id = ErrorHandler.log_error_with_context(error, context, error_type) + + # 構建響應 + response = { + "success": False, + "error_id": error_id, + "error_type": error_type.value, + "message": ErrorHandler.format_user_error(error, error_type, context, include_technical=not for_user) + } + + # 添加解決建議 + if include_solutions: + solutions = ErrorHandler.get_error_solutions(error_type) + response["solutions"] = solutions # 即使為空列表也添加 + + # 添加上下文(僅用於調試) + if context and not for_user: + response["context"] = context + + return response diff --git a/src/mcp_feedback_enhanced/utils/resource_manager.py b/src/mcp_feedback_enhanced/utils/resource_manager.py new file mode 100644 index 0000000..205c180 --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/resource_manager.py @@ -0,0 +1,719 @@ +""" +統一資源管理器 +============== + +提供統一的資源管理功能,包括: +- 臨時文件和目錄管理 +- 進程生命週期追蹤 +- 自動資源清理 +- 資源使用監控 +""" + +import os +import sys +import time +import atexit +import shutil +import tempfile +import threading +import subprocess +import weakref +from pathlib import Path +from typing import Set, Dict, Any, Optional, List, Union +from ..debug import debug_log +from .error_handler import ErrorHandler, ErrorType + + +class ResourceType: + """資源類型常量""" + TEMP_FILE = "temp_file" + TEMP_DIR = "temp_dir" + PROCESS = "process" + FILE_HANDLE = "file_handle" + + +class ResourceManager: + """統一資源管理器 - 提供完整的資源生命週期管理""" + + _instance = None + _lock = threading.Lock() + + def __new__(cls): + """單例模式實現""" + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + """初始化資源管理器""" + if hasattr(self, '_initialized'): + return + + self._initialized = True + + # 資源追蹤集合 + self.temp_files: Set[str] = set() + self.temp_dirs: Set[str] = set() + self.processes: Dict[int, Dict[str, Any]] = {} + self.file_handles: Set[Any] = set() + + # 資源統計 + self.stats = { + "temp_files_created": 0, + "temp_dirs_created": 0, + "processes_registered": 0, + "cleanup_runs": 0, + "last_cleanup": None + } + + # 配置 + self.auto_cleanup_enabled = True + self.cleanup_interval = 300 # 5分鐘 + self.temp_file_max_age = 3600 # 1小時 + + # 清理線程 + self._cleanup_thread: Optional[threading.Thread] = None + self._stop_cleanup = threading.Event() + + # 註冊退出清理 + atexit.register(self.cleanup_all) + + # 啟動自動清理 + self._start_auto_cleanup() + + debug_log("ResourceManager 初始化完成") + + def create_temp_file( + self, + suffix: str = "", + prefix: str = "mcp_", + dir: Optional[str] = None, + text: bool = True + ) -> str: + """ + 創建臨時文件並追蹤 + + Args: + suffix: 文件後綴 + prefix: 文件前綴 + dir: 臨時目錄,None 使用系統默認 + text: 是否為文本模式 + + Returns: + str: 臨時文件路徑 + """ + try: + # 創建臨時文件 + fd, temp_path = tempfile.mkstemp( + suffix=suffix, + prefix=prefix, + dir=dir, + text=text + ) + os.close(fd) # 關閉文件描述符 + + # 追蹤文件 + self.temp_files.add(temp_path) + self.stats["temp_files_created"] += 1 + + debug_log(f"創建臨時文件: {temp_path}") + return temp_path + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "創建臨時文件", "suffix": suffix, "prefix": prefix}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"創建臨時文件失敗 [錯誤ID: {error_id}]: {e}") + raise + + def create_temp_dir( + self, + suffix: str = "", + prefix: str = "mcp_", + dir: Optional[str] = None + ) -> str: + """ + 創建臨時目錄並追蹤 + + Args: + suffix: 目錄後綴 + prefix: 目錄前綴 + dir: 父目錄,None 使用系統默認 + + Returns: + str: 臨時目錄路徑 + """ + try: + # 創建臨時目錄 + temp_dir = tempfile.mkdtemp( + suffix=suffix, + prefix=prefix, + dir=dir + ) + + # 追蹤目錄 + self.temp_dirs.add(temp_dir) + self.stats["temp_dirs_created"] += 1 + + debug_log(f"創建臨時目錄: {temp_dir}") + return temp_dir + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "創建臨時目錄", "suffix": suffix, "prefix": prefix}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"創建臨時目錄失敗 [錯誤ID: {error_id}]: {e}") + raise + + def register_process( + self, + process: Union[subprocess.Popen, int], + description: str = "", + auto_cleanup: bool = True + ) -> int: + """ + 註冊進程追蹤 + + Args: + process: 進程對象或 PID + description: 進程描述 + auto_cleanup: 是否自動清理 + + Returns: + int: 進程 PID + """ + try: + if isinstance(process, subprocess.Popen): + pid = process.pid + process_obj = process + else: + pid = process + process_obj = None + + # 註冊進程 + self.processes[pid] = { + "process": process_obj, + "description": description, + "auto_cleanup": auto_cleanup, + "registered_at": time.time(), + "last_check": time.time() + } + + self.stats["processes_registered"] += 1 + + debug_log(f"註冊進程追蹤: PID {pid} - {description}") + return pid + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "註冊進程", "description": description}, + error_type=ErrorType.PROCESS + ) + debug_log(f"註冊進程失敗 [錯誤ID: {error_id}]: {e}") + raise + + def register_file_handle(self, file_handle: Any) -> None: + """ + 註冊文件句柄追蹤 + + Args: + file_handle: 文件句柄對象 + """ + try: + # 使用弱引用避免循環引用 + self.file_handles.add(weakref.ref(file_handle)) + debug_log(f"註冊文件句柄: {type(file_handle).__name__}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "註冊文件句柄"}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"註冊文件句柄失敗 [錯誤ID: {error_id}]: {e}") + + def unregister_temp_file(self, file_path: str) -> bool: + """ + 取消臨時文件追蹤 + + Args: + file_path: 文件路徑 + + Returns: + bool: 是否成功取消追蹤 + """ + try: + if file_path in self.temp_files: + self.temp_files.remove(file_path) + debug_log(f"取消臨時文件追蹤: {file_path}") + return True + return False + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "取消文件追蹤", "file_path": file_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"取消文件追蹤失敗 [錯誤ID: {error_id}]: {e}") + return False + + def unregister_process(self, pid: int) -> bool: + """ + 取消進程追蹤 + + Args: + pid: 進程 PID + + Returns: + bool: 是否成功取消追蹤 + """ + try: + if pid in self.processes: + del self.processes[pid] + debug_log(f"取消進程追蹤: PID {pid}") + return True + return False + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "取消進程追蹤", "pid": pid}, + error_type=ErrorType.PROCESS + ) + debug_log(f"取消進程追蹤失敗 [錯誤ID: {error_id}]: {e}") + return False + + def cleanup_temp_files(self, max_age: Optional[int] = None) -> int: + """ + 清理臨時文件 + + Args: + max_age: 最大文件年齡(秒),None 使用默認值 + + Returns: + int: 清理的文件數量 + """ + if max_age is None: + max_age = self.temp_file_max_age + + cleaned_count = 0 + current_time = time.time() + files_to_remove = set() + + for file_path in self.temp_files.copy(): + try: + if not os.path.exists(file_path): + files_to_remove.add(file_path) + continue + + # 檢查文件年齡 + file_age = current_time - os.path.getmtime(file_path) + if file_age > max_age: + os.remove(file_path) + files_to_remove.add(file_path) + cleaned_count += 1 + debug_log(f"清理過期臨時文件: {file_path}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理臨時文件", "file_path": file_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理臨時文件失敗 [錯誤ID: {error_id}]: {e}") + files_to_remove.add(file_path) # 移除無效追蹤 + + # 移除已清理的文件追蹤 + self.temp_files -= files_to_remove + + return cleaned_count + + def cleanup_temp_dirs(self) -> int: + """ + 清理臨時目錄 + + Returns: + int: 清理的目錄數量 + """ + cleaned_count = 0 + dirs_to_remove = set() + + for dir_path in self.temp_dirs.copy(): + try: + if not os.path.exists(dir_path): + dirs_to_remove.add(dir_path) + continue + + # 嘗試刪除目錄 + shutil.rmtree(dir_path) + dirs_to_remove.add(dir_path) + cleaned_count += 1 + debug_log(f"清理臨時目錄: {dir_path}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理臨時目錄", "dir_path": dir_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理臨時目錄失敗 [錯誤ID: {error_id}]: {e}") + dirs_to_remove.add(dir_path) # 移除無效追蹤 + + # 移除已清理的目錄追蹤 + self.temp_dirs -= dirs_to_remove + + return cleaned_count + + def cleanup_processes(self, force: bool = False) -> int: + """ + 清理進程 + + Args: + force: 是否強制終止進程 + + Returns: + int: 清理的進程數量 + """ + cleaned_count = 0 + processes_to_remove = [] + + for pid, process_info in self.processes.copy().items(): + try: + process_obj = process_info.get("process") + auto_cleanup = process_info.get("auto_cleanup", True) + + if not auto_cleanup: + continue + + # 檢查進程是否還在運行 + if process_obj and hasattr(process_obj, 'poll'): + if process_obj.poll() is None: # 進程還在運行 + if force: + debug_log(f"強制終止進程: PID {pid}") + process_obj.kill() + else: + debug_log(f"優雅終止進程: PID {pid}") + process_obj.terminate() + + # 等待進程結束 + try: + process_obj.wait(timeout=5) + cleaned_count += 1 + except subprocess.TimeoutExpired: + if not force: + debug_log(f"進程 {pid} 優雅終止超時,強制終止") + process_obj.kill() + process_obj.wait(timeout=3) + cleaned_count += 1 + + processes_to_remove.append(pid) + else: + # 使用 psutil 檢查進程 + try: + import psutil + if psutil.pid_exists(pid): + proc = psutil.Process(pid) + if force: + proc.kill() + else: + proc.terminate() + proc.wait(timeout=5) + cleaned_count += 1 + processes_to_remove.append(pid) + except ImportError: + debug_log("psutil 不可用,跳過進程檢查") + processes_to_remove.append(pid) + except Exception as e: + debug_log(f"清理進程 {pid} 失敗: {e}") + processes_to_remove.append(pid) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理進程", "pid": pid}, + error_type=ErrorType.PROCESS + ) + debug_log(f"清理進程失敗 [錯誤ID: {error_id}]: {e}") + processes_to_remove.append(pid) + + # 移除已清理的進程追蹤 + for pid in processes_to_remove: + self.processes.pop(pid, None) + + return cleaned_count + + def cleanup_file_handles(self) -> int: + """ + 清理文件句柄 + + Returns: + int: 清理的句柄數量 + """ + cleaned_count = 0 + handles_to_remove = set() + + for handle_ref in self.file_handles.copy(): + try: + handle = handle_ref() + if handle is None: + # 弱引用已失效 + handles_to_remove.add(handle_ref) + continue + + # 嘗試關閉文件句柄 + if hasattr(handle, 'close') and not handle.closed: + handle.close() + cleaned_count += 1 + debug_log(f"關閉文件句柄: {type(handle).__name__}") + + handles_to_remove.add(handle_ref) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理文件句柄"}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理文件句柄失敗 [錯誤ID: {error_id}]: {e}") + handles_to_remove.add(handle_ref) + + # 移除已清理的句柄追蹤 + self.file_handles -= handles_to_remove + + return cleaned_count + + def cleanup_all(self, force: bool = False) -> Dict[str, int]: + """ + 清理所有資源 + + Args: + force: 是否強制清理 + + Returns: + Dict[str, int]: 清理統計 + """ + debug_log("開始全面資源清理...") + + results = { + "temp_files": 0, + "temp_dirs": 0, + "processes": 0, + "file_handles": 0 + } + + try: + # 清理文件句柄 + results["file_handles"] = self.cleanup_file_handles() + + # 清理進程 + results["processes"] = self.cleanup_processes(force=force) + + # 清理臨時文件 + results["temp_files"] = self.cleanup_temp_files(max_age=0) # 清理所有文件 + + # 清理臨時目錄 + results["temp_dirs"] = self.cleanup_temp_dirs() + + # 更新統計 + self.stats["cleanup_runs"] += 1 + self.stats["last_cleanup"] = time.time() + + total_cleaned = sum(results.values()) + debug_log(f"資源清理完成,共清理 {total_cleaned} 個資源: {results}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "全面資源清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"全面資源清理失敗 [錯誤ID: {error_id}]: {e}") + + return results + + def _start_auto_cleanup(self) -> None: + """啟動自動清理線程""" + if not self.auto_cleanup_enabled or self._cleanup_thread: + return + + def cleanup_worker(): + """清理工作線程""" + while not self._stop_cleanup.wait(self.cleanup_interval): + try: + # 執行定期清理 + self.cleanup_temp_files() + self._check_process_health() + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"自動清理失敗 [錯誤ID: {error_id}]: {e}") + + self._cleanup_thread = threading.Thread( + target=cleanup_worker, + name="ResourceManager-AutoCleanup", + daemon=True + ) + self._cleanup_thread.start() + debug_log("自動清理線程已啟動") + + def _check_process_health(self) -> None: + """檢查進程健康狀態""" + current_time = time.time() + + for pid, process_info in self.processes.items(): + try: + process_obj = process_info.get("process") + last_check = process_info.get("last_check", current_time) + + # 每分鐘檢查一次 + if current_time - last_check < 60: + continue + + # 更新檢查時間 + process_info["last_check"] = current_time + + # 檢查進程是否還在運行 + if process_obj and hasattr(process_obj, 'poll'): + if process_obj.poll() is not None: + # 進程已結束,移除追蹤 + debug_log(f"檢測到進程 {pid} 已結束,移除追蹤") + self.unregister_process(pid) + + except Exception as e: + debug_log(f"檢查進程 {pid} 健康狀態失敗: {e}") + + def stop_auto_cleanup(self) -> None: + """停止自動清理""" + if self._cleanup_thread: + self._stop_cleanup.set() + self._cleanup_thread.join(timeout=5) + self._cleanup_thread = None + debug_log("自動清理線程已停止") + + def get_resource_stats(self) -> Dict[str, Any]: + """ + 獲取資源統計信息 + + Returns: + Dict[str, Any]: 資源統計 + """ + current_stats = self.stats.copy() + current_stats.update({ + "current_temp_files": len(self.temp_files), + "current_temp_dirs": len(self.temp_dirs), + "current_processes": len(self.processes), + "current_file_handles": len(self.file_handles), + "auto_cleanup_enabled": self.auto_cleanup_enabled, + "cleanup_interval": self.cleanup_interval, + "temp_file_max_age": self.temp_file_max_age + }) + + return current_stats + + def get_detailed_info(self) -> Dict[str, Any]: + """ + 獲取詳細資源信息 + + Returns: + Dict[str, Any]: 詳細資源信息 + """ + return { + "temp_files": list(self.temp_files), + "temp_dirs": list(self.temp_dirs), + "processes": { + pid: { + "description": info.get("description", ""), + "auto_cleanup": info.get("auto_cleanup", True), + "registered_at": info.get("registered_at", 0), + "last_check": info.get("last_check", 0) + } + for pid, info in self.processes.items() + }, + "file_handles_count": len(self.file_handles), + "stats": self.get_resource_stats() + } + + def configure( + self, + auto_cleanup_enabled: Optional[bool] = None, + cleanup_interval: Optional[int] = None, + temp_file_max_age: Optional[int] = None + ) -> None: + """ + 配置資源管理器 + + Args: + auto_cleanup_enabled: 是否啟用自動清理 + cleanup_interval: 清理間隔(秒) + temp_file_max_age: 臨時文件最大年齡(秒) + """ + if auto_cleanup_enabled is not None: + old_enabled = self.auto_cleanup_enabled + self.auto_cleanup_enabled = auto_cleanup_enabled + + if old_enabled and not auto_cleanup_enabled: + self.stop_auto_cleanup() + elif not old_enabled and auto_cleanup_enabled: + self._start_auto_cleanup() + elif auto_cleanup_enabled and self._cleanup_thread is None: + # 如果啟用了自動清理但線程不存在,重新啟動 + self._start_auto_cleanup() + + if cleanup_interval is not None: + self.cleanup_interval = max(60, cleanup_interval) # 最小1分鐘 + + if temp_file_max_age is not None: + self.temp_file_max_age = max(300, temp_file_max_age) # 最小5分鐘 + + debug_log(f"ResourceManager 配置已更新: auto_cleanup={self.auto_cleanup_enabled}, " + f"interval={self.cleanup_interval}, max_age={self.temp_file_max_age}") + + +# 全局資源管理器實例 +_resource_manager = None + + +def get_resource_manager() -> ResourceManager: + """ + 獲取全局資源管理器實例 + + Returns: + ResourceManager: 資源管理器實例 + """ + global _resource_manager + if _resource_manager is None: + _resource_manager = ResourceManager() + return _resource_manager + + +# 便捷函數 +def create_temp_file(suffix: str = "", prefix: str = "mcp_", **kwargs) -> str: + """創建臨時文件的便捷函數""" + return get_resource_manager().create_temp_file(suffix=suffix, prefix=prefix, **kwargs) + + +def create_temp_dir(suffix: str = "", prefix: str = "mcp_", **kwargs) -> str: + """創建臨時目錄的便捷函數""" + return get_resource_manager().create_temp_dir(suffix=suffix, prefix=prefix, **kwargs) + + +def register_process(process: Union[subprocess.Popen, int], description: str = "", **kwargs) -> int: + """註冊進程的便捷函數""" + return get_resource_manager().register_process(process, description=description, **kwargs) + + +def cleanup_all_resources(force: bool = False) -> Dict[str, int]: + """清理所有資源的便捷函數""" + return get_resource_manager().cleanup_all(force=force) diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 1e940bd..a837aed 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -20,14 +20,18 @@ from pathlib import Path from typing import Dict, Optional import uuid -from fastapi import FastAPI +from fastapi import FastAPI, Request, Response from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from fastapi.middleware.gzip import GZipMiddleware import uvicorn from .models import WebFeedbackSession, FeedbackResult from .routes import setup_routes from .utils import find_free_port, get_browser_opener +from .utils.port_manager import PortManager +from .utils.compression_config import get_compression_manager +from ..utils.error_handler import ErrorHandler, ErrorType from ..debug import web_debug_log as debug_log from ..i18n import get_i18n_manager @@ -56,10 +60,17 @@ class WebUIManager: else: debug_log(f"未設定 MCP_WEB_PORT 環境變數,使用預設端口 {preferred_port}") - # 優先使用指定端口,確保 localStorage 的一致性 - self.port = port or find_free_port(preferred_port=preferred_port) + # 使用增強的端口管理,支持自動清理 + self.port = port or PortManager.find_free_port_enhanced( + preferred_port=preferred_port, + auto_cleanup=True, + host=self.host + ) self.app = FastAPI(title="MCP Feedback Enhanced") + # 設置壓縮和緩存中間件 + self._setup_compression_middleware() + # 重構:使用單一活躍會話而非會話字典 self.current_session: Optional[WebFeedbackSession] = None self.sessions: Dict[str, WebFeedbackSession] = {} # 保留用於向後兼容 @@ -83,6 +94,48 @@ class WebUIManager: debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動") + def _setup_compression_middleware(self): + """設置壓縮和緩存中間件""" + # 獲取壓縮管理器 + compression_manager = get_compression_manager() + config = compression_manager.config + + # 添加 Gzip 壓縮中間件 + self.app.add_middleware( + GZipMiddleware, + minimum_size=config.minimum_size + ) + + # 添加緩存和壓縮統計中間件 + @self.app.middleware("http") + async def compression_and_cache_middleware(request: Request, call_next): + """壓縮和緩存中間件""" + response = await call_next(request) + + # 添加緩存頭 + if not config.should_exclude_path(request.url.path): + cache_headers = config.get_cache_headers(request.url.path) + for key, value in cache_headers.items(): + response.headers[key] = value + + # 更新壓縮統計(如果可能) + try: + content_length = int(response.headers.get('content-length', 0)) + content_encoding = response.headers.get('content-encoding', '') + was_compressed = 'gzip' in content_encoding + + if content_length > 0: + # 估算原始大小(如果已壓縮,假設壓縮比為 30%) + original_size = content_length if not was_compressed else int(content_length / 0.7) + compression_manager.update_stats(original_size, content_length, was_compressed) + except (ValueError, TypeError): + # 忽略統計錯誤,不影響正常響應 + pass + + return response + + debug_log("壓縮和緩存中間件設置完成") + def _setup_static_files(self): """設置靜態文件服務""" # Web UI 靜態文件 @@ -262,16 +315,44 @@ class WebUIManager: if e.errno == 10048: # Windows: 位址已在使用中 retry_count += 1 if retry_count < max_retries: - debug_log(f"端口 {self.port} 被占用,嘗試下一個端口") - self.port = find_free_port(self.port + 1) + debug_log(f"端口 {self.port} 被占用,使用增強端口管理查找新端口") + # 使用增強的端口管理查找新端口 + try: + self.port = PortManager.find_free_port_enhanced( + preferred_port=self.port + 1, + auto_cleanup=False, # 啟動時不自動清理,避免誤殺其他服務 + host=self.host + ) + debug_log(f"找到新的可用端口: {self.port}") + except RuntimeError as port_error: + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + port_error, + context={"operation": "端口查找", "current_port": self.port}, + error_type=ErrorType.NETWORK + ) + debug_log(f"無法找到可用端口 [錯誤ID: {error_id}]: {port_error}") + break else: debug_log("已達到最大重試次數,無法啟動伺服器") break else: - debug_log(f"伺服器啟動錯誤: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "伺服器啟動", "host": self.host, "port": self.port}, + error_type=ErrorType.NETWORK + ) + debug_log(f"伺服器啟動錯誤 [錯誤ID: {error_id}]: {e}") break except Exception as e: - debug_log(f"伺服器運行錯誤: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "伺服器運行", "host": self.host, "port": self.port}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"伺服器運行錯誤 [錯誤ID: {error_id}]: {e}") break # 在新線程中啟動伺服器 diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index 746c2f2..6089f63 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -18,6 +18,7 @@ from typing import Dict, List, Optional from fastapi import WebSocket from ...debug import web_debug_log as debug_log +from ...utils.resource_manager import get_resource_manager, register_process class SessionStatus(Enum): @@ -60,6 +61,9 @@ class WebFeedbackSession: # 確保臨時目錄存在 TEMP_DIR.mkdir(parents=True, exist_ok=True) + # 獲取資源管理器實例 + self.resource_manager = get_resource_manager() + def update_status(self, status: SessionStatus, message: str = None): """更新會話狀態""" self.status = status @@ -249,6 +253,13 @@ class WebFeedbackSession: universal_newlines=True ) + # 註冊進程到資源管理器 + register_process( + self.process, + description=f"WebFeedbackSession-{self.session_id}-command", + auto_cleanup=True + ) + # 在背景線程中讀取輸出 async def read_output(): loop = asyncio.get_event_loop() @@ -281,7 +292,10 @@ class WebFeedbackSession: # 等待進程完成 if self.process: exit_code = self.process.wait() - + + # 從資源管理器取消註冊進程 + self.resource_manager.unregister_process(self.process.pid) + # 發送命令完成信號 if self.websocket: try: diff --git a/src/mcp_feedback_enhanced/web/utils/compression_config.py b/src/mcp_feedback_enhanced/web/utils/compression_config.py new file mode 100644 index 0000000..5522c33 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/utils/compression_config.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +壓縮配置管理器 +============== + +管理 Web UI 的 Gzip 壓縮配置和靜態文件緩存策略。 +支援可配置的壓縮參數和性能優化選項。 +""" + +import os +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass +from pathlib import Path + + +@dataclass +class CompressionConfig: + """壓縮配置類""" + + # Gzip 壓縮設定 + minimum_size: int = 1000 # 最小壓縮大小(bytes) + compression_level: int = 6 # 壓縮級別 (1-9, 6為平衡點) + + # 緩存設定 + static_cache_max_age: int = 3600 # 靜態文件緩存時間(秒) + api_cache_max_age: int = 0 # API 響應緩存時間(秒,0表示不緩存) + + # 支援的 MIME 類型 + compressible_types: List[str] = None + + # 排除的路徑 + exclude_paths: List[str] = None + + def __post_init__(self): + """初始化後處理""" + if self.compressible_types is None: + self.compressible_types = [ + 'text/html', + 'text/css', + 'text/javascript', + 'text/plain', + 'application/json', + 'application/javascript', + 'application/xml', + 'application/rss+xml', + 'application/atom+xml', + 'image/svg+xml' + ] + + if self.exclude_paths is None: + self.exclude_paths = [ + '/ws', # WebSocket 連接 + '/api/ws', # WebSocket API + '/health', # 健康檢查 + ] + + @classmethod + def from_env(cls) -> 'CompressionConfig': + """從環境變數創建配置""" + return cls( + minimum_size=int(os.getenv('MCP_GZIP_MIN_SIZE', '1000')), + compression_level=int(os.getenv('MCP_GZIP_LEVEL', '6')), + static_cache_max_age=int(os.getenv('MCP_STATIC_CACHE_AGE', '3600')), + api_cache_max_age=int(os.getenv('MCP_API_CACHE_AGE', '0')) + ) + + def should_compress(self, content_type: str, content_length: int) -> bool: + """判斷是否應該壓縮""" + if content_length < self.minimum_size: + return False + + if not content_type: + return False + + # 檢查 MIME 類型 + for mime_type in self.compressible_types: + if content_type.startswith(mime_type): + return True + + return False + + def should_exclude_path(self, path: str) -> bool: + """判斷路徑是否應該排除壓縮""" + for exclude_path in self.exclude_paths: + if path.startswith(exclude_path): + return True + return False + + def get_cache_headers(self, path: str) -> Dict[str, str]: + """獲取緩存頭""" + headers = {} + + if path.startswith('/static/'): + # 靜態文件緩存 + headers['Cache-Control'] = f'public, max-age={self.static_cache_max_age}' + headers['Expires'] = self._get_expires_header(self.static_cache_max_age) + elif path.startswith('/api/') and self.api_cache_max_age > 0: + # API 緩存(如果啟用) + headers['Cache-Control'] = f'public, max-age={self.api_cache_max_age}' + headers['Expires'] = self._get_expires_header(self.api_cache_max_age) + else: + # 其他路徑不緩存 + headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + headers['Pragma'] = 'no-cache' + headers['Expires'] = '0' + + return headers + + def _get_expires_header(self, max_age: int) -> str: + """生成 Expires 頭""" + from datetime import datetime, timedelta + expires_time = datetime.utcnow() + timedelta(seconds=max_age) + return expires_time.strftime('%a, %d %b %Y %H:%M:%S GMT') + + def get_compression_stats(self) -> Dict[str, any]: + """獲取壓縮配置統計""" + return { + 'minimum_size': self.minimum_size, + 'compression_level': self.compression_level, + 'static_cache_max_age': self.static_cache_max_age, + 'compressible_types_count': len(self.compressible_types), + 'exclude_paths_count': len(self.exclude_paths), + 'compressible_types': self.compressible_types, + 'exclude_paths': self.exclude_paths + } + + +class CompressionManager: + """壓縮管理器""" + + def __init__(self, config: Optional[CompressionConfig] = None): + self.config = config or CompressionConfig.from_env() + self._stats = { + 'requests_total': 0, + 'requests_compressed': 0, + 'bytes_original': 0, + 'bytes_compressed': 0, + 'compression_ratio': 0.0 + } + + def update_stats(self, original_size: int, compressed_size: int, was_compressed: bool): + """更新壓縮統計""" + self._stats['requests_total'] += 1 + self._stats['bytes_original'] += original_size + + if was_compressed: + self._stats['requests_compressed'] += 1 + self._stats['bytes_compressed'] += compressed_size + else: + self._stats['bytes_compressed'] += original_size + + # 計算壓縮比率 + if self._stats['bytes_original'] > 0: + self._stats['compression_ratio'] = ( + 1 - self._stats['bytes_compressed'] / self._stats['bytes_original'] + ) * 100 + + def get_stats(self) -> Dict[str, any]: + """獲取壓縮統計""" + stats = self._stats.copy() + stats['compression_percentage'] = ( + self._stats['requests_compressed'] / max(self._stats['requests_total'], 1) * 100 + ) + return stats + + def reset_stats(self): + """重置統計""" + self._stats = { + 'requests_total': 0, + 'requests_compressed': 0, + 'bytes_original': 0, + 'bytes_compressed': 0, + 'compression_ratio': 0.0 + } + + +# 全域壓縮管理器實例 +_compression_manager: Optional[CompressionManager] = None + + +def get_compression_manager() -> CompressionManager: + """獲取全域壓縮管理器實例""" + global _compression_manager + if _compression_manager is None: + _compression_manager = CompressionManager() + return _compression_manager diff --git a/src/mcp_feedback_enhanced/web/utils/compression_monitor.py b/src/mcp_feedback_enhanced/web/utils/compression_monitor.py new file mode 100644 index 0000000..475bada --- /dev/null +++ b/src/mcp_feedback_enhanced/web/utils/compression_monitor.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +壓縮性能監控工具 +================ + +監控 Gzip 壓縮的性能效果,包括壓縮比率、響應時間和文件大小統計。 +提供實時性能數據和優化建議。 +""" + +import time +import threading +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass, field +from datetime import datetime, timedelta +import json + + +@dataclass +class CompressionMetrics: + """壓縮指標數據類""" + timestamp: datetime + path: str + original_size: int + compressed_size: int + compression_ratio: float + response_time: float + content_type: str + was_compressed: bool + + +@dataclass +class CompressionSummary: + """壓縮摘要統計""" + total_requests: int = 0 + compressed_requests: int = 0 + total_original_bytes: int = 0 + total_compressed_bytes: int = 0 + average_compression_ratio: float = 0.0 + average_response_time: float = 0.0 + compression_percentage: float = 0.0 + bandwidth_saved: int = 0 + top_compressed_paths: List[Tuple[str, float]] = field(default_factory=list) + + +class CompressionMonitor: + """壓縮性能監控器""" + + def __init__(self, max_metrics: int = 1000): + self.max_metrics = max_metrics + self.metrics: List[CompressionMetrics] = [] + self.lock = threading.Lock() + self._start_time = datetime.now() + + # 路徑統計 + self.path_stats: Dict[str, Dict] = {} + + # 內容類型統計 + self.content_type_stats: Dict[str, Dict] = {} + + def record_request(self, + path: str, + original_size: int, + compressed_size: int, + response_time: float, + content_type: str = "", + was_compressed: bool = False): + """記錄請求的壓縮數據""" + + compression_ratio = 0.0 + if original_size > 0 and was_compressed: + compression_ratio = (1 - compressed_size / original_size) * 100 + + metric = CompressionMetrics( + timestamp=datetime.now(), + path=path, + original_size=original_size, + compressed_size=compressed_size, + compression_ratio=compression_ratio, + response_time=response_time, + content_type=content_type, + was_compressed=was_compressed + ) + + with self.lock: + self.metrics.append(metric) + + # 限制記錄數量 + if len(self.metrics) > self.max_metrics: + self.metrics = self.metrics[-self.max_metrics:] + + # 更新路徑統計 + self._update_path_stats(metric) + + # 更新內容類型統計 + self._update_content_type_stats(metric) + + def _update_path_stats(self, metric: CompressionMetrics): + """更新路徑統計""" + path = metric.path + if path not in self.path_stats: + self.path_stats[path] = { + 'requests': 0, + 'compressed_requests': 0, + 'total_original_bytes': 0, + 'total_compressed_bytes': 0, + 'total_response_time': 0.0, + 'best_compression_ratio': 0.0 + } + + stats = self.path_stats[path] + stats['requests'] += 1 + stats['total_original_bytes'] += metric.original_size + stats['total_compressed_bytes'] += metric.compressed_size + stats['total_response_time'] += metric.response_time + + if metric.was_compressed: + stats['compressed_requests'] += 1 + stats['best_compression_ratio'] = max( + stats['best_compression_ratio'], + metric.compression_ratio + ) + + def _update_content_type_stats(self, metric: CompressionMetrics): + """更新內容類型統計""" + content_type = metric.content_type or 'unknown' + if content_type not in self.content_type_stats: + self.content_type_stats[content_type] = { + 'requests': 0, + 'compressed_requests': 0, + 'total_original_bytes': 0, + 'total_compressed_bytes': 0, + 'average_compression_ratio': 0.0 + } + + stats = self.content_type_stats[content_type] + stats['requests'] += 1 + stats['total_original_bytes'] += metric.original_size + stats['total_compressed_bytes'] += metric.compressed_size + + if metric.was_compressed: + stats['compressed_requests'] += 1 + + # 重新計算平均壓縮比 + if stats['total_original_bytes'] > 0: + stats['average_compression_ratio'] = ( + 1 - stats['total_compressed_bytes'] / stats['total_original_bytes'] + ) * 100 + + def get_summary(self, time_window: Optional[timedelta] = None) -> CompressionSummary: + """獲取壓縮摘要統計""" + with self.lock: + metrics = self.metrics + + # 如果指定時間窗口,過濾數據 + if time_window: + cutoff_time = datetime.now() - time_window + metrics = [m for m in metrics if m.timestamp >= cutoff_time] + + if not metrics: + return CompressionSummary() + + total_requests = len(metrics) + compressed_requests = sum(1 for m in metrics if m.was_compressed) + total_original_bytes = sum(m.original_size for m in metrics) + total_compressed_bytes = sum(m.compressed_size for m in metrics) + total_response_time = sum(m.response_time for m in metrics) + + # 計算統計數據 + compression_percentage = (compressed_requests / total_requests * 100) if total_requests > 0 else 0 + average_compression_ratio = 0.0 + bandwidth_saved = 0 + + if total_original_bytes > 0: + average_compression_ratio = (1 - total_compressed_bytes / total_original_bytes) * 100 + bandwidth_saved = total_original_bytes - total_compressed_bytes + + average_response_time = total_response_time / total_requests if total_requests > 0 else 0 + + # 獲取壓縮效果最好的路徑 + top_compressed_paths = self._get_top_compressed_paths() + + return CompressionSummary( + total_requests=total_requests, + compressed_requests=compressed_requests, + total_original_bytes=total_original_bytes, + total_compressed_bytes=total_compressed_bytes, + average_compression_ratio=average_compression_ratio, + average_response_time=average_response_time, + compression_percentage=compression_percentage, + bandwidth_saved=bandwidth_saved, + top_compressed_paths=top_compressed_paths + ) + + def _get_top_compressed_paths(self, limit: int = 5) -> List[Tuple[str, float]]: + """獲取壓縮效果最好的路徑""" + path_ratios = [] + + for path, stats in self.path_stats.items(): + if stats['compressed_requests'] > 0 and stats['total_original_bytes'] > 0: + compression_ratio = ( + 1 - stats['total_compressed_bytes'] / stats['total_original_bytes'] + ) * 100 + path_ratios.append((path, compression_ratio)) + + # 按壓縮比排序 + path_ratios.sort(key=lambda x: x[1], reverse=True) + return path_ratios[:limit] + + def get_path_stats(self) -> Dict[str, Dict]: + """獲取路徑統計""" + with self.lock: + return self.path_stats.copy() + + def get_content_type_stats(self) -> Dict[str, Dict]: + """獲取內容類型統計""" + with self.lock: + return self.content_type_stats.copy() + + def get_recent_metrics(self, limit: int = 100) -> List[CompressionMetrics]: + """獲取最近的指標數據""" + with self.lock: + return self.metrics[-limit:] if self.metrics else [] + + def reset_stats(self): + """重置統計數據""" + with self.lock: + self.metrics.clear() + self.path_stats.clear() + self.content_type_stats.clear() + self._start_time = datetime.now() + + def export_stats(self) -> Dict: + """導出統計數據為字典格式""" + summary = self.get_summary() + + return { + 'summary': { + 'total_requests': summary.total_requests, + 'compressed_requests': summary.compressed_requests, + 'compression_percentage': round(summary.compression_percentage, 2), + 'average_compression_ratio': round(summary.average_compression_ratio, 2), + 'bandwidth_saved_mb': round(summary.bandwidth_saved / (1024 * 1024), 2), + 'average_response_time_ms': round(summary.average_response_time * 1000, 2), + 'monitoring_duration_hours': round( + (datetime.now() - self._start_time).total_seconds() / 3600, 2 + ) + }, + 'top_compressed_paths': [ + {'path': path, 'compression_ratio': round(ratio, 2)} + for path, ratio in summary.top_compressed_paths + ], + 'path_stats': { + path: { + 'requests': stats['requests'], + 'compression_percentage': round( + stats['compressed_requests'] / stats['requests'] * 100, 2 + ) if stats['requests'] > 0 else 0, + 'average_response_time_ms': round( + stats['total_response_time'] / stats['requests'] * 1000, 2 + ) if stats['requests'] > 0 else 0, + 'bandwidth_saved_kb': round( + (stats['total_original_bytes'] - stats['total_compressed_bytes']) / 1024, 2 + ) + } + for path, stats in self.path_stats.items() + }, + 'content_type_stats': { + content_type: { + 'requests': stats['requests'], + 'compression_percentage': round( + stats['compressed_requests'] / stats['requests'] * 100, 2 + ) if stats['requests'] > 0 else 0, + 'average_compression_ratio': round(stats['average_compression_ratio'], 2) + } + for content_type, stats in self.content_type_stats.items() + } + } + + +# 全域監控器實例 +_compression_monitor: Optional[CompressionMonitor] = None + + +def get_compression_monitor() -> CompressionMonitor: + """獲取全域壓縮監控器實例""" + global _compression_monitor + if _compression_monitor is None: + _compression_monitor = CompressionMonitor() + return _compression_monitor diff --git a/src/mcp_feedback_enhanced/web/utils/port_manager.py b/src/mcp_feedback_enhanced/web/utils/port_manager.py new file mode 100644 index 0000000..635909d --- /dev/null +++ b/src/mcp_feedback_enhanced/web/utils/port_manager.py @@ -0,0 +1,307 @@ +""" +端口管理工具模組 + +提供增強的端口管理功能,包括: +- 智能端口查找 +- 進程檢測和清理 +- 端口衝突解決 +""" + +import socket +import subprocess +import platform +import psutil +import time +from typing import Optional, Dict, Any, List +from ...debug import debug_log + + +class PortManager: + """端口管理器 - 提供增強的端口管理功能""" + + @staticmethod + def find_process_using_port(port: int) -> Optional[Dict[str, Any]]: + """ + 查找占用指定端口的進程 + + Args: + port: 要檢查的端口號 + + Returns: + Dict[str, Any]: 進程信息字典,包含 pid, name, cmdline 等 + None: 如果沒有進程占用該端口 + """ + try: + for conn in psutil.net_connections(kind='inet'): + if conn.laddr.port == port and conn.status == psutil.CONN_LISTEN: + try: + process = psutil.Process(conn.pid) + return { + 'pid': conn.pid, + 'name': process.name(), + 'cmdline': ' '.join(process.cmdline()), + 'create_time': process.create_time(), + 'status': process.status() + } + except (psutil.NoSuchProcess, psutil.AccessDenied): + # 進程可能已經結束或無權限訪問 + continue + except Exception as e: + debug_log(f"查找端口 {port} 占用進程時發生錯誤: {e}") + + return None + + @staticmethod + def kill_process_on_port(port: int, force: bool = False) -> bool: + """ + 終止占用指定端口的進程 + + Args: + port: 要清理的端口號 + force: 是否強制終止進程 + + Returns: + bool: 是否成功終止進程 + """ + process_info = PortManager.find_process_using_port(port) + if not process_info: + debug_log(f"端口 {port} 沒有被任何進程占用") + return True + + try: + pid = process_info['pid'] + process = psutil.Process(pid) + process_name = process_info['name'] + + debug_log(f"發現進程 {process_name} (PID: {pid}) 占用端口 {port}") + + # 檢查是否是自己的進程(避免誤殺) + if 'mcp-feedback-enhanced' in process_info['cmdline'].lower(): + debug_log(f"檢測到 MCP Feedback Enhanced 相關進程,嘗試優雅終止") + + if force: + debug_log(f"強制終止進程 {process_name} (PID: {pid})") + process.kill() + else: + debug_log(f"優雅終止進程 {process_name} (PID: {pid})") + process.terminate() + + # 等待進程結束 + try: + process.wait(timeout=5) + debug_log(f"成功終止進程 {process_name} (PID: {pid})") + return True + except psutil.TimeoutExpired: + if not force: + debug_log(f"優雅終止超時,強制終止進程 {process_name} (PID: {pid})") + process.kill() + process.wait(timeout=3) + return True + else: + debug_log(f"強制終止進程 {process_name} (PID: {pid}) 失敗") + return False + + except (psutil.NoSuchProcess, psutil.AccessDenied) as e: + debug_log(f"無法終止進程 (PID: {process_info['pid']}): {e}") + return False + except Exception as e: + debug_log(f"終止端口 {port} 占用進程時發生錯誤: {e}") + return False + + @staticmethod + def is_port_available(host: str, port: int) -> bool: + """ + 檢查端口是否可用 + + Args: + host: 主機地址 + port: 端口號 + + Returns: + bool: 端口是否可用 + """ + try: + # 首先嘗試不使用 SO_REUSEADDR 來檢測端口 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind((host, port)) + return True + except OSError: + # 如果綁定失敗,再檢查是否真的有進程在監聽 + # 使用 psutil 檢查是否有進程在監聽該端口 + try: + import psutil + for conn in psutil.net_connections(kind='inet'): + if (conn.laddr.port == port and + conn.laddr.ip in [host, '0.0.0.0', '::'] and + conn.status == psutil.CONN_LISTEN): + return False + # 沒有找到監聽的進程,可能是臨時占用,認為可用 + return True + except Exception: + # 如果 psutil 檢查失敗,保守地認為端口不可用 + return False + + @staticmethod + def find_free_port_enhanced( + preferred_port: int = 8765, + auto_cleanup: bool = True, + host: str = "127.0.0.1", + max_attempts: int = 100 + ) -> int: + """ + 增強的端口查找功能 + + Args: + preferred_port: 偏好端口號 + auto_cleanup: 是否自動清理占用端口的進程 + host: 主機地址 + max_attempts: 最大嘗試次數 + + Returns: + int: 可用的端口號 + + Raises: + RuntimeError: 如果找不到可用端口 + """ + # 首先嘗試偏好端口 + if PortManager.is_port_available(host, preferred_port): + debug_log(f"偏好端口 {preferred_port} 可用") + return preferred_port + + # 如果偏好端口被占用且啟用自動清理 + if auto_cleanup: + debug_log(f"偏好端口 {preferred_port} 被占用,嘗試清理占用進程") + process_info = PortManager.find_process_using_port(preferred_port) + + if process_info: + debug_log(f"端口 {preferred_port} 被進程 {process_info['name']} (PID: {process_info['pid']}) 占用") + + # 詢問用戶是否清理(在實際使用中可能需要配置選項) + if PortManager._should_cleanup_process(process_info): + if PortManager.kill_process_on_port(preferred_port): + # 等待一下讓端口釋放 + time.sleep(1) + if PortManager.is_port_available(host, preferred_port): + debug_log(f"成功清理端口 {preferred_port},現在可用") + return preferred_port + + # 如果偏好端口仍不可用,尋找其他端口 + debug_log(f"偏好端口 {preferred_port} 不可用,尋找其他可用端口") + + for i in range(max_attempts): + port = preferred_port + i + 1 + if PortManager.is_port_available(host, port): + debug_log(f"找到可用端口: {port}") + return port + + # 如果向上查找失敗,嘗試向下查找 + for i in range(1, min(preferred_port - 1024, max_attempts)): + port = preferred_port - i + if port < 1024: # 避免使用系統保留端口 + break + if PortManager.is_port_available(host, port): + debug_log(f"找到可用端口: {port}") + return port + + raise RuntimeError( + f"無法在 {preferred_port}±{max_attempts} 範圍內找到可用端口。" + f"請檢查是否有過多進程占用端口,或手動指定其他端口。" + ) + + @staticmethod + def _should_cleanup_process(process_info: Dict[str, Any]) -> bool: + """ + 判斷是否應該清理指定進程 + + Args: + process_info: 進程信息字典 + + Returns: + bool: 是否應該清理該進程 + """ + # 檢查是否是 MCP Feedback Enhanced 相關進程 + cmdline = process_info.get('cmdline', '').lower() + process_name = process_info.get('name', '').lower() + + # 如果是自己的進程,允許清理 + if any(keyword in cmdline for keyword in ['mcp-feedback-enhanced', 'mcp_feedback_enhanced']): + return True + + # 如果是 Python 進程且命令行包含相關關鍵字 + if 'python' in process_name and any(keyword in cmdline for keyword in ['uvicorn', 'fastapi']): + return True + + # 其他情況下,為了安全起見,不自動清理 + debug_log(f"進程 {process_info['name']} (PID: {process_info['pid']}) 不是 MCP 相關進程,跳過自動清理") + return False + + @staticmethod + def get_port_status(port: int, host: str = "127.0.0.1") -> Dict[str, Any]: + """ + 獲取端口狀態信息 + + Args: + port: 端口號 + host: 主機地址 + + Returns: + Dict[str, Any]: 端口狀態信息 + """ + status = { + 'port': port, + 'host': host, + 'available': False, + 'process': None, + 'error': None + } + + try: + # 檢查端口是否可用 + status['available'] = PortManager.is_port_available(host, port) + + # 如果不可用,查找占用進程 + if not status['available']: + status['process'] = PortManager.find_process_using_port(port) + + except Exception as e: + status['error'] = str(e) + debug_log(f"獲取端口 {port} 狀態時發生錯誤: {e}") + + return status + + @staticmethod + def list_listening_ports(start_port: int = 8000, end_port: int = 9000) -> List[Dict[str, Any]]: + """ + 列出指定範圍內正在監聽的端口 + + Args: + start_port: 起始端口 + end_port: 結束端口 + + Returns: + List[Dict[str, Any]]: 監聽端口列表 + """ + listening_ports = [] + + try: + for conn in psutil.net_connections(kind='inet'): + if (conn.status == psutil.CONN_LISTEN and + start_port <= conn.laddr.port <= end_port): + + try: + process = psutil.Process(conn.pid) + port_info = { + 'port': conn.laddr.port, + 'host': conn.laddr.ip, + 'pid': conn.pid, + 'process_name': process.name(), + 'cmdline': ' '.join(process.cmdline()) + } + listening_ports.append(port_info) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + except Exception as e: + debug_log(f"列出監聽端口時發生錯誤: {e}") + + return listening_ports diff --git a/tests/test_error_handler.py b/tests/test_error_handler.py new file mode 100644 index 0000000..3a973ac --- /dev/null +++ b/tests/test_error_handler.py @@ -0,0 +1,253 @@ +""" +錯誤處理框架測試模組 + +測試 ErrorHandler 類的各項功能,包括: +- 錯誤類型自動分類 +- 用戶友好錯誤信息生成 +- 國際化支持 +- 錯誤上下文記錄 +""" + +import pytest +import sys +import os +from unittest.mock import patch, MagicMock + +# 添加 src 目錄到 Python 路徑 +sys.path.insert(0, 'src') + +from mcp_feedback_enhanced.utils.error_handler import ErrorHandler, ErrorType, ErrorSeverity + + +class TestErrorHandler: + """錯誤處理器測試類""" + + def test_classify_error_network(self): + """測試網絡錯誤分類""" + # 測試 ConnectionError + error = ConnectionError("Connection failed") + assert ErrorHandler.classify_error(error) == ErrorType.NETWORK + + # 測試包含網絡關鍵字的錯誤(不包含 timeout) + error = Exception("socket connection failed") + assert ErrorHandler.classify_error(error) == ErrorType.NETWORK + + def test_classify_error_file_io(self): + """測試文件 I/O 錯誤分類""" + # 測試 FileNotFoundError + error = FileNotFoundError("No such file or directory") + assert ErrorHandler.classify_error(error) == ErrorType.FILE_IO + + # 測試包含文件關鍵字的錯誤(不包含權限關鍵字) + error = Exception("file not found") + assert ErrorHandler.classify_error(error) == ErrorType.FILE_IO + + def test_classify_error_timeout(self): + """測試超時錯誤分類""" + error = TimeoutError("Operation timed out") + assert ErrorHandler.classify_error(error) == ErrorType.TIMEOUT + + error = Exception("timeout occurred") + assert ErrorHandler.classify_error(error) == ErrorType.TIMEOUT + + def test_classify_error_permission(self): + """測試權限錯誤分類""" + error = PermissionError("Access denied") + assert ErrorHandler.classify_error(error) == ErrorType.PERMISSION + + error = Exception("access denied") + assert ErrorHandler.classify_error(error) == ErrorType.PERMISSION + + def test_classify_error_validation(self): + """測試驗證錯誤分類""" + error = ValueError("Invalid value") + assert ErrorHandler.classify_error(error) == ErrorType.VALIDATION + + error = TypeError("Wrong type") + assert ErrorHandler.classify_error(error) == ErrorType.VALIDATION + + def test_classify_error_default_system(self): + """測試默認系統錯誤分類""" + error = Exception("Some completely unknown issue") + assert ErrorHandler.classify_error(error) == ErrorType.SYSTEM + + def test_format_user_error_basic(self): + """測試基本用戶友好錯誤信息生成""" + error = ConnectionError("Connection failed") + result = ErrorHandler.format_user_error(error) + + assert "❌" in result + assert "網絡連接出現問題" in result or "网络连接出现问题" in result or "Network connection issue" in result + + def test_format_user_error_with_context(self): + """測試帶上下文的錯誤信息生成""" + error = FileNotFoundError("File not found") + context = { + "operation": "文件讀取", + "file_path": "/path/to/file.txt" + } + + result = ErrorHandler.format_user_error(error, context=context) + + assert "❌" in result + assert "文件讀取" in result or "文件读取" in result or "文件讀取" in result + assert "/path/to/file.txt" in result + + def test_format_user_error_with_technical_details(self): + """測試包含技術細節的錯誤信息""" + error = ValueError("Invalid input") + result = ErrorHandler.format_user_error(error, include_technical=True) + + assert "❌" in result + assert "ValueError" in result + assert "Invalid input" in result + + def test_get_error_solutions(self): + """測試獲取錯誤解決方案""" + solutions = ErrorHandler.get_error_solutions(ErrorType.NETWORK) + + assert isinstance(solutions, list) + assert len(solutions) > 0 + # 應該包含網絡相關的解決方案 + solutions_text = " ".join(solutions).lower() + assert any(keyword in solutions_text for keyword in ["網絡", "网络", "network", "連接", "连接", "connection"]) + + def test_log_error_with_context(self): + """測試帶上下文的錯誤記錄""" + error = Exception("Test error") + context = {"operation": "測試操作", "user": "test_user"} + + error_id = ErrorHandler.log_error_with_context(error, context=context) + + assert isinstance(error_id, str) + assert error_id.startswith("ERR_") + assert len(error_id.split("_")) == 3 # ERR_timestamp_id + + def test_create_error_response(self): + """測試創建標準化錯誤響應""" + error = ConnectionError("Network error") + context = {"operation": "網絡請求"} + + response = ErrorHandler.create_error_response(error, context=context) + + assert isinstance(response, dict) + assert response["success"] is False + assert "error_id" in response + assert "error_type" in response + assert "message" in response + assert response["error_type"] == ErrorType.NETWORK.value + assert "solutions" in response + + def test_create_error_response_for_user(self): + """測試為用戶界面創建錯誤響應""" + error = FileNotFoundError("File not found") + + response = ErrorHandler.create_error_response(error, for_user=True) + + assert response["success"] is False + assert "context" not in response # 用戶界面不應包含技術上下文 + assert "❌" in response["message"] # 應該包含用戶友好的格式 + + @patch('mcp_feedback_enhanced.utils.error_handler.ErrorHandler.get_i18n_error_message') + def test_language_support(self, mock_get_message): + """測試多語言支持""" + error = ConnectionError("Network error") + + # 測試繁體中文 + mock_get_message.return_value = "網絡連接出現問題" + result = ErrorHandler.format_user_error(error) + assert "網絡連接出現問題" in result + + # 測試簡體中文 + mock_get_message.return_value = "网络连接出现问题" + result = ErrorHandler.format_user_error(error) + assert "网络连接出现问题" in result + + # 測試英文 + mock_get_message.return_value = "Network connection issue" + result = ErrorHandler.format_user_error(error) + assert "Network connection issue" in result + + def test_error_severity_logging(self): + """測試錯誤嚴重程度記錄""" + error = Exception("Critical system error") + + # 測試高嚴重程度錯誤 + error_id = ErrorHandler.log_error_with_context( + error, + severity=ErrorSeverity.CRITICAL + ) + + assert isinstance(error_id, str) + assert error_id.startswith("ERR_") + + def test_get_current_language_fallback(self): + """測試語言獲取回退機制""" + # 由於 i18n 系統可能會覆蓋環境變數,我們主要測試函數不會拋出異常 + language = ErrorHandler.get_current_language() + assert isinstance(language, str) + assert len(language) > 0 + + # 測試語言代碼格式 + assert language in ["zh-TW", "zh-CN", "en"] or "-" in language + + def test_i18n_integration(self): + """測試國際化系統集成""" + # 測試當 i18n 系統不可用時的回退 + error_type = ErrorType.NETWORK + + # 測試獲取錯誤信息 + message = ErrorHandler.get_i18n_error_message(error_type) + assert isinstance(message, str) + assert len(message) > 0 + + # 測試獲取解決方案 + solutions = ErrorHandler.get_i18n_error_solutions(error_type) + assert isinstance(solutions, list) + + def test_error_context_preservation(self): + """測試錯誤上下文保存""" + error = Exception("Test error") + context = { + "operation": "測試操作", + "file_path": "/test/path", + "user_id": "test_user", + "timestamp": "2025-01-05" + } + + error_id = ErrorHandler.log_error_with_context(error, context=context) + + # 驗證錯誤 ID 格式 + assert isinstance(error_id, str) + assert error_id.startswith("ERR_") + + # 上下文應該被記錄到調試日誌中(通過 debug_log) + # 這裡我們主要驗證函數不會拋出異常 + + def test_json_rpc_safety(self): + """測試不影響 JSON RPC 通信""" + # 錯誤處理應該只記錄到 stderr(通過 debug_log) + # 不應該影響 stdout 或 JSON RPC 響應 + + error = Exception("Test error for JSON RPC safety") + context = {"operation": "JSON RPC 測試"} + + # 這些操作不應該影響 stdout + error_id = ErrorHandler.log_error_with_context(error, context=context) + user_message = ErrorHandler.format_user_error(error) + response = ErrorHandler.create_error_response(error) + + # 驗證返回值類型正確 + assert isinstance(error_id, str) + assert isinstance(user_message, str) + assert isinstance(response, dict) + + # 驗證不會拋出異常 + assert error_id.startswith("ERR_") + assert "❌" in user_message + assert response["success"] is False + + +if __name__ == '__main__': + # 運行測試 + pytest.main([__file__, '-v']) diff --git a/tests/test_gzip_compression.py b/tests/test_gzip_compression.py new file mode 100644 index 0000000..fc1d337 --- /dev/null +++ b/tests/test_gzip_compression.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Gzip 壓縮功能測試 +================ + +測試 FastAPI Gzip 壓縮中間件的功能,包括: +- 壓縮效果驗證 +- WebSocket 兼容性 +- 靜態文件緩存 +- 性能提升測試 +""" + +import pytest +import asyncio +import gzip +import json +from unittest.mock import Mock, patch +from fastapi.testclient import TestClient +from fastapi import FastAPI, Response +from fastapi.middleware.gzip import GZipMiddleware + +from src.mcp_feedback_enhanced.web.utils.compression_config import ( + CompressionConfig, CompressionManager, get_compression_manager +) +from src.mcp_feedback_enhanced.web.utils.compression_monitor import ( + CompressionMonitor, get_compression_monitor +) + + +class TestCompressionConfig: + """測試壓縮配置類""" + + def test_default_config(self): + """測試預設配置""" + config = CompressionConfig() + + assert config.minimum_size == 1000 + assert config.compression_level == 6 + assert config.static_cache_max_age == 3600 + assert config.api_cache_max_age == 0 + assert 'text/html' in config.compressible_types + assert 'application/json' in config.compressible_types + assert '/ws' in config.exclude_paths + + def test_from_env(self): + """測試從環境變數創建配置""" + with patch.dict('os.environ', { + 'MCP_GZIP_MIN_SIZE': '2000', + 'MCP_GZIP_LEVEL': '9', + 'MCP_STATIC_CACHE_AGE': '7200' + }): + config = CompressionConfig.from_env() + + assert config.minimum_size == 2000 + assert config.compression_level == 9 + assert config.static_cache_max_age == 7200 + + def test_should_compress(self): + """測試壓縮判斷邏輯""" + config = CompressionConfig() + + # 應該壓縮的情況 + assert config.should_compress('text/html', 2000) == True + assert config.should_compress('application/json', 1500) == True + + # 不應該壓縮的情況 + assert config.should_compress('text/html', 500) == False # 太小 + assert config.should_compress('image/jpeg', 2000) == False # 不支援的類型 + assert config.should_compress('', 2000) == False # 無內容類型 + + def test_should_exclude_path(self): + """測試路徑排除邏輯""" + config = CompressionConfig() + + assert config.should_exclude_path('/ws') == True + assert config.should_exclude_path('/api/ws') == True + assert config.should_exclude_path('/health') == True + assert config.should_exclude_path('/static/css/style.css') == False + assert config.should_exclude_path('/api/feedback') == False + + def test_get_cache_headers(self): + """測試緩存頭生成""" + config = CompressionConfig() + + # 靜態文件 + static_headers = config.get_cache_headers('/static/css/style.css') + assert 'Cache-Control' in static_headers + assert 'public, max-age=3600' in static_headers['Cache-Control'] + + # API 路徑(預設不緩存) + api_headers = config.get_cache_headers('/api/feedback') + assert 'no-cache' in api_headers['Cache-Control'] + + # 其他路徑 + other_headers = config.get_cache_headers('/feedback') + assert 'no-cache' in other_headers['Cache-Control'] + + +class TestCompressionManager: + """測試壓縮管理器""" + + def test_manager_initialization(self): + """測試管理器初始化""" + manager = CompressionManager() + + assert manager.config is not None + assert manager._stats['requests_total'] == 0 + assert manager._stats['requests_compressed'] == 0 + + def test_update_stats(self): + """測試統計更新""" + manager = CompressionManager() + + # 測試壓縮請求 + manager.update_stats(1000, 600, True) + stats = manager.get_stats() + + assert stats['requests_total'] == 1 + assert stats['requests_compressed'] == 1 + assert stats['bytes_original'] == 1000 + assert stats['bytes_compressed'] == 600 + assert stats['compression_ratio'] == 40.0 # (1000-600)/1000 * 100 + + # 測試未壓縮請求 + manager.update_stats(500, 500, False) + stats = manager.get_stats() + + assert stats['requests_total'] == 2 + assert stats['requests_compressed'] == 1 + assert stats['compression_percentage'] == 50.0 # 1/2 * 100 + + def test_reset_stats(self): + """測試統計重置""" + manager = CompressionManager() + manager.update_stats(1000, 600, True) + + manager.reset_stats() + stats = manager.get_stats() + + assert stats['requests_total'] == 0 + assert stats['requests_compressed'] == 0 + assert stats['compression_ratio'] == 0.0 + + +class TestCompressionMonitor: + """測試壓縮監控器""" + + def test_monitor_initialization(self): + """測試監控器初始化""" + monitor = CompressionMonitor() + + assert monitor.max_metrics == 1000 + assert len(monitor.metrics) == 0 + assert len(monitor.path_stats) == 0 + + def test_record_request(self): + """測試請求記錄""" + monitor = CompressionMonitor() + + monitor.record_request( + path='/static/css/style.css', + original_size=2000, + compressed_size=1200, + response_time=0.05, + content_type='text/css', + was_compressed=True + ) + + assert len(monitor.metrics) == 1 + metric = monitor.metrics[0] + assert metric.path == '/static/css/style.css' + assert metric.compression_ratio == 40.0 # (2000-1200)/2000 * 100 + + # 檢查路徑統計 + path_stats = monitor.get_path_stats() + assert '/static/css/style.css' in path_stats + assert path_stats['/static/css/style.css']['requests'] == 1 + assert path_stats['/static/css/style.css']['compressed_requests'] == 1 + + def test_get_summary(self): + """測試摘要統計""" + monitor = CompressionMonitor() + + # 記錄多個請求 + monitor.record_request('/static/css/style.css', 2000, 1200, 0.05, 'text/css', True) + monitor.record_request('/static/js/app.js', 3000, 1800, 0.08, 'application/javascript', True) + monitor.record_request('/api/feedback', 500, 500, 0.02, 'application/json', False) + + summary = monitor.get_summary() + + assert summary.total_requests == 3 + assert summary.compressed_requests == 2 + assert abs(summary.compression_percentage - 66.67) < 0.01 # 2/3 * 100 (約) + assert summary.bandwidth_saved == 2000 # (2000-1200) + (3000-1800) + 0 = 800 + 1200 + 0 = 2000 + + def test_export_stats(self): + """測試統計導出""" + monitor = CompressionMonitor() + + monitor.record_request('/static/css/style.css', 2000, 1200, 0.05, 'text/css', True) + + exported = monitor.export_stats() + + assert 'summary' in exported + assert 'top_compressed_paths' in exported + assert 'path_stats' in exported + assert 'content_type_stats' in exported + + assert exported['summary']['total_requests'] == 1 + assert exported['summary']['compressed_requests'] == 1 + + +class TestGzipIntegration: + """測試 Gzip 壓縮集成""" + + def create_test_app(self): + """創建測試應用""" + app = FastAPI() + + # 添加 Gzip 中間件 + app.add_middleware(GZipMiddleware, minimum_size=100) + + @app.get("/test-large") + async def test_large(): + # 返回大於最小壓縮大小的內容 + return {"data": "x" * 1000} + + @app.get("/test-small") + async def test_small(): + # 返回小於最小壓縮大小的內容 + return {"data": "small"} + + @app.get("/test-html") + async def test_html(): + html_content = "" + "content " * 100 + "" + return Response(content=html_content, media_type="text/html") + + return app + + def test_gzip_compression_large_content(self): + """測試大內容的 Gzip 壓縮""" + app = self.create_test_app() + client = TestClient(app) + + # 請求壓縮 + response = client.get("/test-large", headers={"Accept-Encoding": "gzip"}) + + assert response.status_code == 200 + assert response.headers.get("content-encoding") == "gzip" + + # 驗證內容正確性 + data = response.json() + assert "data" in data + assert len(data["data"]) == 1000 + + def test_gzip_compression_small_content(self): + """測試小內容不壓縮""" + app = self.create_test_app() + client = TestClient(app) + + response = client.get("/test-small", headers={"Accept-Encoding": "gzip"}) + + assert response.status_code == 200 + # 小內容不應該被壓縮 + assert response.headers.get("content-encoding") != "gzip" + + def test_gzip_compression_html_content(self): + """測試 HTML 內容壓縮""" + app = self.create_test_app() + client = TestClient(app) + + response = client.get("/test-html", headers={"Accept-Encoding": "gzip"}) + + assert response.status_code == 200 + assert response.headers.get("content-encoding") == "gzip" + assert response.headers.get("content-type") == "text/html; charset=utf-8" + + def test_no_compression_without_accept_encoding(self): + """測試不支援壓縮的客戶端""" + app = self.create_test_app() + client = TestClient(app) + + # FastAPI 的 TestClient 預設會添加 Accept-Encoding,所以我們測試明確拒絕壓縮 + response = client.get("/test-large", headers={"Accept-Encoding": "identity"}) + + assert response.status_code == 200 + # 當明確要求不壓縮時,應該不會有 gzip 編碼 + # 注意:某些情況下 FastAPI 仍可能壓縮,這是正常行為 + + +class TestWebSocketCompatibility: + """測試 WebSocket 兼容性""" + + def test_websocket_not_compressed(self): + """測試 WebSocket 連接不受壓縮影響""" + # 這個測試確保 WebSocket 路徑被正確排除 + config = CompressionConfig() + + # WebSocket 路徑應該被排除 + assert config.should_exclude_path('/ws') == True + assert config.should_exclude_path('/api/ws') == True + + # 確保 WebSocket 不會被壓縮配置影響 + assert not config.should_compress('application/json', 1000) or config.should_exclude_path('/ws') + + +@pytest.mark.asyncio +async def test_compression_performance(): + """測試壓縮性能""" + # 創建測試數據 + test_data = {"message": "test " * 1000} # 大約 5KB 的 JSON + json_data = json.dumps(test_data) + + # 手動壓縮測試 + compressed_data = gzip.compress(json_data.encode('utf-8')) + + # 驗證壓縮效果 + original_size = len(json_data.encode('utf-8')) + compressed_size = len(compressed_data) + compression_ratio = (1 - compressed_size / original_size) * 100 + + # 壓縮比應該大於 50%(JSON 數據通常壓縮效果很好) + assert compression_ratio > 50 + assert compressed_size < original_size + + # 驗證解壓縮正確性 + decompressed_data = gzip.decompress(compressed_data).decode('utf-8') + assert decompressed_data == json_data + + +def test_global_instances(): + """測試全域實例""" + # 測試壓縮管理器全域實例 + manager1 = get_compression_manager() + manager2 = get_compression_manager() + assert manager1 is manager2 + + # 測試壓縮監控器全域實例 + monitor1 = get_compression_monitor() + monitor2 = get_compression_monitor() + assert monitor1 is monitor2 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_port_manager.py b/tests/test_port_manager.py new file mode 100644 index 0000000..d1ccc61 --- /dev/null +++ b/tests/test_port_manager.py @@ -0,0 +1,249 @@ +""" +端口管理器測試模組 + +測試 PortManager 類的各項功能,包括: +- 端口可用性檢測 +- 進程查找和清理 +- 增強端口查找 +""" + +import pytest +import socket +import time +import threading +import subprocess +import sys +from unittest.mock import patch, MagicMock + +# 添加 src 目錄到 Python 路徑 +sys.path.insert(0, 'src') + +from mcp_feedback_enhanced.web.utils.port_manager import PortManager + + +class TestPortManager: + """端口管理器測試類""" + + def test_is_port_available_free_port(self): + """測試檢測空閒端口""" + # 找一個肯定空閒的端口 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', 0)) + free_port = s.getsockname()[1] + + # 測試該端口是否被檢測為可用 + assert PortManager.is_port_available('127.0.0.1', free_port) is True + + def test_is_port_available_occupied_port(self): + """測試檢測被占用的端口""" + # 創建一個占用端口的 socket + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('127.0.0.1', 0)) + occupied_port = server_socket.getsockname()[1] + server_socket.listen(1) + + try: + # 測試該端口是否被檢測為不可用 + assert PortManager.is_port_available('127.0.0.1', occupied_port) is False + finally: + server_socket.close() + + def test_find_free_port_enhanced_preferred_available(self): + """測試當偏好端口可用時的行為""" + # 找一個空閒端口作為偏好端口 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', 0)) + preferred_port = s.getsockname()[1] + + # 測試是否返回偏好端口 + result_port = PortManager.find_free_port_enhanced( + preferred_port=preferred_port, + auto_cleanup=False + ) + assert result_port == preferred_port + + def test_find_free_port_enhanced_preferred_occupied(self): + """測試當偏好端口被占用時的行為""" + # 創建一個占用端口的 socket + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('127.0.0.1', 0)) + occupied_port = server_socket.getsockname()[1] + server_socket.listen(1) + + try: + # 測試是否返回其他可用端口 + result_port = PortManager.find_free_port_enhanced( + preferred_port=occupied_port, + auto_cleanup=False + ) + assert result_port != occupied_port + assert result_port > occupied_port # 應該向上查找 + + # 驗證返回的端口確實可用 + assert PortManager.is_port_available('127.0.0.1', result_port) is True + finally: + server_socket.close() + + def test_find_process_using_port_no_process(self): + """測試查找沒有進程占用的端口""" + # 找一個空閒端口 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', 0)) + free_port = s.getsockname()[1] + + # 測試是否正確返回 None + result = PortManager.find_process_using_port(free_port) + assert result is None + + def test_find_process_using_port_with_process(self): + """測試查找有進程占用的端口""" + # 創建一個簡單的測試服務器 + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('127.0.0.1', 0)) + test_port = server_socket.getsockname()[1] + server_socket.listen(1) + + try: + # 測試是否能找到進程信息 + result = PortManager.find_process_using_port(test_port) + + if result: # 如果找到了進程(在某些環境下可能找不到) + assert isinstance(result, dict) + assert 'pid' in result + assert 'name' in result + assert 'cmdline' in result + assert isinstance(result['pid'], int) + assert result['pid'] > 0 + finally: + server_socket.close() + + def test_get_port_status_available(self): + """測試獲取可用端口的狀態""" + # 找一個空閒端口 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', 0)) + free_port = s.getsockname()[1] + + status = PortManager.get_port_status(free_port) + + assert status['port'] == free_port + assert status['host'] == '127.0.0.1' + assert status['available'] is True + assert status['process'] is None + assert status['error'] is None + + def test_get_port_status_occupied(self): + """測試獲取被占用端口的狀態""" + # 創建一個占用端口的 socket + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('127.0.0.1', 0)) + occupied_port = server_socket.getsockname()[1] + server_socket.listen(1) + + try: + status = PortManager.get_port_status(occupied_port) + + assert status['port'] == occupied_port + assert status['host'] == '127.0.0.1' + assert status['available'] is False + # process 可能為 None(取決於系統權限) + assert status['error'] is None + finally: + server_socket.close() + + def test_list_listening_ports(self): + """測試列出監聽端口""" + # 創建幾個測試服務器 + servers = [] + test_ports = [] + + try: + for i in range(2): + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('127.0.0.1', 0)) + port = server_socket.getsockname()[1] + server_socket.listen(1) + + servers.append(server_socket) + test_ports.append(port) + + # 測試列出監聽端口 + min_port = min(test_ports) - 10 + max_port = max(test_ports) + 10 + + listening_ports = PortManager.list_listening_ports(min_port, max_port) + + # 驗證結果 + assert isinstance(listening_ports, list) + + # 檢查我們的測試端口是否在列表中 + found_ports = [p['port'] for p in listening_ports] + for test_port in test_ports: + if test_port in found_ports: + # 找到了我們的端口,驗證信息完整性 + port_info = next(p for p in listening_ports if p['port'] == test_port) + assert 'host' in port_info + assert 'pid' in port_info + assert 'process_name' in port_info + assert 'cmdline' in port_info + + finally: + # 清理測試服務器 + for server in servers: + server.close() + + @patch('mcp_feedback_enhanced.web.utils.port_manager.psutil.Process') + def test_should_cleanup_process_mcp_process(self, mock_process): + """測試是否應該清理 MCP 相關進程""" + # 模擬 MCP 相關進程 + process_info = { + 'pid': 1234, + 'name': 'python.exe', + 'cmdline': 'python -m mcp-feedback-enhanced test --web', + 'create_time': time.time(), + 'status': 'running' + } + + result = PortManager._should_cleanup_process(process_info) + assert result is True + + @patch('mcp_feedback_enhanced.web.utils.port_manager.psutil.Process') + def test_should_cleanup_process_other_process(self, mock_process): + """測試是否應該清理其他進程""" + # 模擬其他進程 + process_info = { + 'pid': 5678, + 'name': 'chrome.exe', + 'cmdline': 'chrome --new-window', + 'create_time': time.time(), + 'status': 'running' + } + + result = PortManager._should_cleanup_process(process_info) + assert result is False + + def test_find_free_port_enhanced_max_attempts(self): + """測試最大嘗試次數限制""" + # 這個測試比較難實現,因為需要占用大量連續端口 + # 我們只測試參數是否正確傳遞 + try: + result = PortManager.find_free_port_enhanced( + preferred_port=65000, # 使用高端口減少衝突 + auto_cleanup=False, + max_attempts=10 + ) + assert isinstance(result, int) + assert 65000 <= result <= 65535 + except RuntimeError: + # 如果真的找不到端口,這也是正常的 + pass + + +if __name__ == '__main__': + # 運行測試 + pytest.main([__file__, '-v']) diff --git a/tests/test_resource_manager.py b/tests/test_resource_manager.py new file mode 100644 index 0000000..bbe10ab --- /dev/null +++ b/tests/test_resource_manager.py @@ -0,0 +1,394 @@ +""" +資源管理器測試模組 + +測試 ResourceManager 類的各項功能,包括: +- 臨時文件和目錄管理 +- 進程註冊和清理 +- 自動清理機制 +- 資源統計和監控 +""" + +import pytest +import os +import sys +import time +import tempfile +import subprocess +import threading +from pathlib import Path +from unittest.mock import patch, MagicMock + +# 添加 src 目錄到 Python 路徑 +sys.path.insert(0, 'src') + +from mcp_feedback_enhanced.utils.resource_manager import ( + ResourceManager, + get_resource_manager, + create_temp_file, + create_temp_dir, + register_process, + cleanup_all_resources +) + + +class TestResourceManager: + """資源管理器測試類""" + + def setup_method(self): + """每個測試方法前的設置""" + # 重置單例實例 + ResourceManager._instance = None + + def test_singleton_pattern(self): + """測試單例模式""" + rm1 = ResourceManager() + rm2 = ResourceManager() + rm3 = get_resource_manager() + + assert rm1 is rm2 + assert rm2 is rm3 + assert id(rm1) == id(rm2) == id(rm3) + + def test_create_temp_file(self): + """測試創建臨時文件""" + rm = get_resource_manager() + + # 測試基本創建 + temp_file = rm.create_temp_file(suffix=".txt", prefix="test_") + + assert isinstance(temp_file, str) + assert os.path.exists(temp_file) + assert temp_file.endswith(".txt") + assert "test_" in os.path.basename(temp_file) + assert temp_file in rm.temp_files + + # 清理 + os.remove(temp_file) + + def test_create_temp_dir(self): + """測試創建臨時目錄""" + rm = get_resource_manager() + + # 測試基本創建 + temp_dir = rm.create_temp_dir(suffix="_test", prefix="test_") + + assert isinstance(temp_dir, str) + assert os.path.exists(temp_dir) + assert os.path.isdir(temp_dir) + assert temp_dir.endswith("_test") + assert "test_" in os.path.basename(temp_dir) + assert temp_dir in rm.temp_dirs + + # 清理 + os.rmdir(temp_dir) + + def test_convenience_functions(self): + """測試便捷函數""" + # 測試 create_temp_file 便捷函數 + temp_file = create_temp_file(suffix=".log", prefix="conv_") + assert isinstance(temp_file, str) + assert os.path.exists(temp_file) + assert temp_file.endswith(".log") + + # 測試 create_temp_dir 便捷函數 + temp_dir = create_temp_dir(suffix="_conv", prefix="conv_") + assert isinstance(temp_dir, str) + assert os.path.exists(temp_dir) + assert os.path.isdir(temp_dir) + + # 清理 + os.remove(temp_file) + os.rmdir(temp_dir) + + def test_register_process_with_popen(self): + """測試註冊 Popen 進程""" + rm = get_resource_manager() + + # 創建一個簡單的進程 + process = subprocess.Popen( + ["python", "-c", "import time; time.sleep(0.1)"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # 註冊進程 + pid = rm.register_process(process, description="測試進程") + + assert pid == process.pid + assert pid in rm.processes + assert rm.processes[pid]["description"] == "測試進程" + assert rm.processes[pid]["process"] is process + + # 等待進程結束 + process.wait() + + def test_register_process_with_pid(self): + """測試註冊 PID""" + rm = get_resource_manager() + + # 使用當前進程的 PID + current_pid = os.getpid() + + # 註冊 PID + registered_pid = rm.register_process(current_pid, description="當前進程") + + assert registered_pid == current_pid + assert current_pid in rm.processes + assert rm.processes[current_pid]["description"] == "當前進程" + assert rm.processes[current_pid]["process"] is None + + def test_unregister_temp_file(self): + """測試取消臨時文件追蹤""" + rm = get_resource_manager() + + # 創建臨時文件 + temp_file = rm.create_temp_file() + assert temp_file in rm.temp_files + + # 取消追蹤 + result = rm.unregister_temp_file(temp_file) + assert result is True + assert temp_file not in rm.temp_files + + # 再次取消追蹤(應該返回 False) + result = rm.unregister_temp_file(temp_file) + assert result is False + + # 清理 + if os.path.exists(temp_file): + os.remove(temp_file) + + def test_unregister_process(self): + """測試取消進程追蹤""" + rm = get_resource_manager() + + # 註冊進程 + current_pid = os.getpid() + rm.register_process(current_pid, description="測試進程") + assert current_pid in rm.processes + + # 取消追蹤 + result = rm.unregister_process(current_pid) + assert result is True + assert current_pid not in rm.processes + + # 再次取消追蹤(應該返回 False) + result = rm.unregister_process(current_pid) + assert result is False + + def test_cleanup_temp_files(self): + """測試清理臨時文件""" + rm = get_resource_manager() + + # 創建多個臨時文件 + temp_files = [] + for i in range(3): + temp_file = rm.create_temp_file(prefix=f"cleanup_test_{i}_") + temp_files.append(temp_file) + + # 確認文件都存在 + for temp_file in temp_files: + assert os.path.exists(temp_file) + assert temp_file in rm.temp_files + + # 執行清理(max_age=0 清理所有文件) + cleaned_count = rm.cleanup_temp_files(max_age=0) + + assert cleaned_count == 3 + for temp_file in temp_files: + assert not os.path.exists(temp_file) + assert temp_file not in rm.temp_files + + def test_cleanup_temp_dirs(self): + """測試清理臨時目錄""" + rm = get_resource_manager() + + # 創建多個臨時目錄 + temp_dirs = [] + for i in range(2): + temp_dir = rm.create_temp_dir(prefix=f"cleanup_test_{i}_") + temp_dirs.append(temp_dir) + + # 確認目錄都存在 + for temp_dir in temp_dirs: + assert os.path.exists(temp_dir) + assert temp_dir in rm.temp_dirs + + # 執行清理 + cleaned_count = rm.cleanup_temp_dirs() + + assert cleaned_count == 2 + for temp_dir in temp_dirs: + assert not os.path.exists(temp_dir) + assert temp_dir not in rm.temp_dirs + + def test_cleanup_all(self): + """測試全面清理""" + rm = get_resource_manager() + + # 創建各種資源 + temp_file = rm.create_temp_file(prefix="cleanup_all_") + temp_dir = rm.create_temp_dir(prefix="cleanup_all_") + + # 註冊進程 + current_pid = os.getpid() + rm.register_process(current_pid, description="測試進程", auto_cleanup=False) + + # 執行全面清理 + results = rm.cleanup_all() + + assert isinstance(results, dict) + assert "temp_files" in results + assert "temp_dirs" in results + assert "processes" in results + assert "file_handles" in results + + # 檢查文件和目錄是否被清理 + assert not os.path.exists(temp_file) + assert not os.path.exists(temp_dir) + assert temp_file not in rm.temp_files + assert temp_dir not in rm.temp_dirs + + # 進程不應該被清理(auto_cleanup=False) + assert current_pid in rm.processes + + def test_get_resource_stats(self): + """測試獲取資源統計""" + rm = get_resource_manager() + + # 創建一些資源 + temp_file = rm.create_temp_file() + temp_dir = rm.create_temp_dir() + rm.register_process(os.getpid(), description="統計測試") + + # 獲取統計 + stats = rm.get_resource_stats() + + assert isinstance(stats, dict) + assert "current_temp_files" in stats + assert "current_temp_dirs" in stats + assert "current_processes" in stats + assert "temp_files_created" in stats + assert "temp_dirs_created" in stats + assert "auto_cleanup_enabled" in stats + + assert stats["current_temp_files"] >= 1 + assert stats["current_temp_dirs"] >= 1 + assert stats["current_processes"] >= 1 + + # 清理 + os.remove(temp_file) + os.rmdir(temp_dir) + + def test_get_detailed_info(self): + """測試獲取詳細信息""" + rm = get_resource_manager() + + # 創建一些資源 + temp_file = rm.create_temp_file(prefix="detail_test_") + rm.register_process(os.getpid(), description="詳細信息測試") + + # 獲取詳細信息 + info = rm.get_detailed_info() + + assert isinstance(info, dict) + assert "temp_files" in info + assert "temp_dirs" in info + assert "processes" in info + assert "stats" in info + + assert temp_file in info["temp_files"] + assert os.getpid() in info["processes"] + assert info["processes"][os.getpid()]["description"] == "詳細信息測試" + + # 清理 + os.remove(temp_file) + + def test_configure(self): + """測試配置功能""" + rm = get_resource_manager() + + # 測試配置更新 + rm.configure( + auto_cleanup_enabled=False, + cleanup_interval=120, + temp_file_max_age=1800 + ) + + assert rm.auto_cleanup_enabled is False + assert rm.cleanup_interval == 120 + assert rm.temp_file_max_age == 1800 + + # 測試最小值限制 + rm.configure( + cleanup_interval=30, # 小於最小值 60 + temp_file_max_age=100 # 小於最小值 300 + ) + + assert rm.cleanup_interval == 60 # 應該被限制為最小值 + assert rm.temp_file_max_age == 300 # 應該被限制為最小值 + + def test_cleanup_all_convenience_function(self): + """測試全面清理便捷函數""" + # 創建一些資源 + temp_file = create_temp_file(prefix="conv_cleanup_") + temp_dir = create_temp_dir(prefix="conv_cleanup_") + + # 執行清理 + results = cleanup_all_resources() + + assert isinstance(results, dict) + assert not os.path.exists(temp_file) + assert not os.path.exists(temp_dir) + + def test_error_handling(self): + """測試錯誤處理""" + rm = get_resource_manager() + + # 測試創建臨時文件時的錯誤處理 + with patch('tempfile.mkstemp', side_effect=OSError("Mock error")): + with pytest.raises(OSError): + rm.create_temp_file() + + # 測試創建臨時目錄時的錯誤處理 + with patch('tempfile.mkdtemp', side_effect=OSError("Mock error")): + with pytest.raises(OSError): + rm.create_temp_dir() + + def test_file_handle_registration(self): + """測試文件句柄註冊""" + rm = get_resource_manager() + + # 創建一個文件句柄 + temp_file = rm.create_temp_file() + with open(temp_file, 'w') as f: + f.write("test") + rm.register_file_handle(f) + + # 檢查是否註冊成功 + assert len(rm.file_handles) > 0 + + # 清理 + os.remove(temp_file) + + def test_auto_cleanup_thread(self): + """測試自動清理線程""" + rm = get_resource_manager() + + # 確保自動清理已啟動 + assert rm.auto_cleanup_enabled is True + assert rm._cleanup_thread is not None + assert rm._cleanup_thread.is_alive() + + # 測試停止自動清理 + rm.stop_auto_cleanup() + assert rm._cleanup_thread is None + + # 重新啟動 + rm.configure(auto_cleanup_enabled=True) + assert rm._cleanup_thread is not None + + +if __name__ == '__main__': + # 運行測試 + pytest.main([__file__, '-v']) From 90deebdee512146beeeb23a1832616bd9b505734 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 02:29:16 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E9=9B=86?= =?UTF-8?q?=E6=88=90=E5=BC=8F=E5=85=A7=E5=AD=98=E7=9B=A3=E6=8E=A7=E7=B3=BB?= =?UTF-8?q?=E7=B5=B1=EF=BC=8C=E6=8F=90=E4=BE=9B=E7=B3=BB=E7=B5=B1=E8=88=87?= =?UTF-8?q?=E9=80=B2=E7=A8=8B=E5=85=A7=E5=AD=98=E4=BD=BF=E7=94=A8=E7=9B=A3?= =?UTF-8?q?=E6=8E=A7=E3=80=81=E6=99=BA=E8=83=BD=E6=B8=85=E7=90=86=E6=A9=9F?= =?UTF-8?q?=E5=88=B6=E3=80=81=E5=85=A7=E5=AD=98=E6=B4=A9=E6=BC=8F=E6=AA=A2?= =?UTF-8?q?=E6=B8=AC=E5=8F=8A=E6=80=A7=E8=83=BD=E5=84=AA=E5=8C=96=E5=BB=BA?= =?UTF-8?q?=E8=AD=B0=E3=80=82=E6=9B=B4=E6=96=B0=E8=B3=87=E6=BA=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8=E8=88=87=20Web=20UI=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=85=A7=E5=AD=98=E7=9B=A3=E6=8E=A7=E9=9B=86=E6=88=90?= =?UTF-8?q?=E8=88=87=E8=AD=A6=E5=91=8A=E5=9B=9E=E8=AA=BF=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B8=AC=E8=A9=A6=E6=A8=A1=E7=B5=84=E4=BB=A5?= =?UTF-8?q?=E7=A2=BA=E4=BF=9D=E5=8A=9F=E8=83=BD=E6=AD=A3=E7=A2=BA=E6=80=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/memory_monitor.py | 526 ++++++++++++++++++ .../utils/resource_manager.py | 82 ++- src/mcp_feedback_enhanced/web/main.py | 31 ++ tests/test_memory_monitor.py | 388 +++++++++++++ 4 files changed, 1025 insertions(+), 2 deletions(-) create mode 100644 src/mcp_feedback_enhanced/utils/memory_monitor.py create mode 100644 tests/test_memory_monitor.py diff --git a/src/mcp_feedback_enhanced/utils/memory_monitor.py b/src/mcp_feedback_enhanced/utils/memory_monitor.py new file mode 100644 index 0000000..644161a --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/memory_monitor.py @@ -0,0 +1,526 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +集成式內存監控系統 +================== + +提供與資源管理器深度集成的內存監控功能,包括: +- 系統和進程內存使用監控 +- 智能清理觸發機制 +- 內存洩漏檢測和趨勢分析 +- 性能優化建議 +""" + +import os +import gc +import time +import threading +import psutil +from typing import Dict, List, Optional, Callable, Any +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from collections import deque +from ..debug import debug_log +from .error_handler import ErrorHandler, ErrorType + + +@dataclass +class MemorySnapshot: + """內存快照數據類""" + timestamp: datetime + system_total: int # 系統總內存 (bytes) + system_available: int # 系統可用內存 (bytes) + system_used: int # 系統已用內存 (bytes) + system_percent: float # 系統內存使用率 (%) + process_rss: int # 進程常駐內存 (bytes) + process_vms: int # 進程虛擬內存 (bytes) + process_percent: float # 進程內存使用率 (%) + gc_objects: int # Python 垃圾回收對象數量 + + +@dataclass +class MemoryAlert: + """內存警告數據類""" + level: str # warning, critical, emergency + message: str + timestamp: datetime + memory_percent: float + recommended_action: str + + +@dataclass +class MemoryStats: + """內存統計數據類""" + monitoring_duration: float # 監控持續時間 (秒) + snapshots_count: int # 快照數量 + average_system_usage: float # 平均系統內存使用率 + peak_system_usage: float # 峰值系統內存使用率 + average_process_usage: float # 平均進程內存使用率 + peak_process_usage: float # 峰值進程內存使用率 + alerts_count: int # 警告數量 + cleanup_triggers: int # 清理觸發次數 + memory_trend: str # 內存趨勢 (stable, increasing, decreasing) + + +class MemoryMonitor: + """集成式內存監控器""" + + def __init__(self, + warning_threshold: float = 0.8, + critical_threshold: float = 0.9, + emergency_threshold: float = 0.95, + monitoring_interval: int = 30, + max_snapshots: int = 1000): + """ + 初始化內存監控器 + + Args: + warning_threshold: 警告閾值 (0.0-1.0) + critical_threshold: 危險閾值 (0.0-1.0) + emergency_threshold: 緊急閾值 (0.0-1.0) + monitoring_interval: 監控間隔 (秒) + max_snapshots: 最大快照數量 + """ + self.warning_threshold = warning_threshold + self.critical_threshold = critical_threshold + self.emergency_threshold = emergency_threshold + self.monitoring_interval = monitoring_interval + self.max_snapshots = max_snapshots + + # 監控狀態 + self.is_monitoring = False + self.monitor_thread: Optional[threading.Thread] = None + self._stop_event = threading.Event() + + # 數據存儲 + self.snapshots: deque = deque(maxlen=max_snapshots) + self.alerts: List[MemoryAlert] = [] + self.max_alerts = 100 + + # 回調函數 + self.cleanup_callbacks: List[Callable] = [] + self.alert_callbacks: List[Callable[[MemoryAlert], None]] = [] + + # 統計數據 + self.start_time: Optional[datetime] = None + self.cleanup_triggers_count = 0 + + # 進程信息 + self.process = psutil.Process() + + debug_log("MemoryMonitor 初始化完成") + + def start_monitoring(self) -> bool: + """ + 開始內存監控 + + Returns: + bool: 是否成功啟動 + """ + if self.is_monitoring: + debug_log("內存監控已在運行") + return True + + try: + self.is_monitoring = True + self.start_time = datetime.now() + self._stop_event.clear() + + self.monitor_thread = threading.Thread( + target=self._monitoring_loop, + name="MemoryMonitor", + daemon=True + ) + self.monitor_thread.start() + + debug_log(f"內存監控已啟動,間隔 {self.monitoring_interval} 秒") + return True + + except Exception as e: + self.is_monitoring = False + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "啟動內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"啟動內存監控失敗 [錯誤ID: {error_id}]: {e}") + return False + + def stop_monitoring(self) -> bool: + """ + 停止內存監控 + + Returns: + bool: 是否成功停止 + """ + if not self.is_monitoring: + debug_log("內存監控未在運行") + return True + + try: + self.is_monitoring = False + self._stop_event.set() + + if self.monitor_thread and self.monitor_thread.is_alive(): + self.monitor_thread.join(timeout=5) + + debug_log("內存監控已停止") + return True + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "停止內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"停止內存監控失敗 [錯誤ID: {error_id}]: {e}") + return False + + def _monitoring_loop(self): + """內存監控主循環""" + debug_log("內存監控循環開始") + + while not self._stop_event.is_set(): + try: + # 收集內存快照 + snapshot = self._collect_memory_snapshot() + self.snapshots.append(snapshot) + + # 檢查內存使用情況 + self._check_memory_usage(snapshot) + + # 等待下次監控 + if self._stop_event.wait(self.monitoring_interval): + break + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存監控循環"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存監控循環錯誤 [錯誤ID: {error_id}]: {e}") + + # 發生錯誤時等待較短時間後重試 + if self._stop_event.wait(5): + break + + debug_log("內存監控循環結束") + + def _collect_memory_snapshot(self) -> MemorySnapshot: + """收集內存快照""" + try: + # 系統內存信息 + system_memory = psutil.virtual_memory() + + # 進程內存信息 + process_memory = self.process.memory_info() + process_percent = self.process.memory_percent() + + # Python 垃圾回收信息 + gc_objects = len(gc.get_objects()) + + return MemorySnapshot( + timestamp=datetime.now(), + system_total=system_memory.total, + system_available=system_memory.available, + system_used=system_memory.used, + system_percent=system_memory.percent, + process_rss=process_memory.rss, + process_vms=process_memory.vms, + process_percent=process_percent, + gc_objects=gc_objects + ) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "收集內存快照"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"收集內存快照失敗 [錯誤ID: {error_id}]: {e}") + raise + + def _check_memory_usage(self, snapshot: MemorySnapshot): + """檢查內存使用情況並觸發相應動作""" + usage_percent = snapshot.system_percent / 100.0 + + # 檢查緊急閾值 + if usage_percent >= self.emergency_threshold: + alert = MemoryAlert( + level="emergency", + message=f"內存使用率達到緊急水平: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="立即執行強制清理和垃圾回收" + ) + self._handle_alert(alert) + self._trigger_emergency_cleanup() + + # 檢查危險閾值 + elif usage_percent >= self.critical_threshold: + alert = MemoryAlert( + level="critical", + message=f"內存使用率達到危險水平: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="執行資源清理和垃圾回收" + ) + self._handle_alert(alert) + self._trigger_cleanup() + + # 檢查警告閾值 + elif usage_percent >= self.warning_threshold: + alert = MemoryAlert( + level="warning", + message=f"內存使用率較高: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="考慮執行輕量級清理" + ) + self._handle_alert(alert) + + def _handle_alert(self, alert: MemoryAlert): + """處理內存警告""" + # 添加到警告列表 + self.alerts.append(alert) + + # 限制警告數量 + if len(self.alerts) > self.max_alerts: + self.alerts = self.alerts[-self.max_alerts:] + + # 調用警告回調 + for callback in self.alert_callbacks: + try: + callback(alert) + except Exception as e: + debug_log(f"警告回調執行失敗: {e}") + + debug_log(f"內存警告 [{alert.level}]: {alert.message}") + + def _trigger_cleanup(self): + """觸發清理操作""" + self.cleanup_triggers_count += 1 + debug_log("觸發內存清理操作") + + # 執行 Python 垃圾回收 + collected = gc.collect() + debug_log(f"垃圾回收清理了 {collected} 個對象") + + # 調用清理回調 + for callback in self.cleanup_callbacks: + try: + callback() + except Exception as e: + debug_log(f"清理回調執行失敗: {e}") + + def _trigger_emergency_cleanup(self): + """觸發緊急清理操作""" + debug_log("觸發緊急內存清理操作") + + # 執行強制垃圾回收 + for _ in range(3): + collected = gc.collect() + debug_log(f"強制垃圾回收清理了 {collected} 個對象") + + # 調用清理回調(強制模式) + for callback in self.cleanup_callbacks: + try: + if hasattr(callback, '__call__'): + # 嘗試傳遞 force 參數 + import inspect + sig = inspect.signature(callback) + if 'force' in sig.parameters: + callback(force=True) + else: + callback() + else: + callback() + except Exception as e: + debug_log(f"緊急清理回調執行失敗: {e}") + + + def add_cleanup_callback(self, callback: Callable): + """添加清理回調函數""" + if callback not in self.cleanup_callbacks: + self.cleanup_callbacks.append(callback) + debug_log("添加清理回調函數") + + def add_alert_callback(self, callback: Callable[[MemoryAlert], None]): + """添加警告回調函數""" + if callback not in self.alert_callbacks: + self.alert_callbacks.append(callback) + debug_log("添加警告回調函數") + + def remove_cleanup_callback(self, callback: Callable): + """移除清理回調函數""" + if callback in self.cleanup_callbacks: + self.cleanup_callbacks.remove(callback) + debug_log("移除清理回調函數") + + def remove_alert_callback(self, callback: Callable[[MemoryAlert], None]): + """移除警告回調函數""" + if callback in self.alert_callbacks: + self.alert_callbacks.remove(callback) + debug_log("移除警告回調函數") + + def get_current_memory_info(self) -> Dict[str, Any]: + """獲取當前內存信息""" + try: + snapshot = self._collect_memory_snapshot() + return { + "timestamp": snapshot.timestamp.isoformat(), + "system": { + "total_gb": round(snapshot.system_total / (1024**3), 2), + "available_gb": round(snapshot.system_available / (1024**3), 2), + "used_gb": round(snapshot.system_used / (1024**3), 2), + "usage_percent": round(snapshot.system_percent, 1) + }, + "process": { + "rss_mb": round(snapshot.process_rss / (1024**2), 2), + "vms_mb": round(snapshot.process_vms / (1024**2), 2), + "usage_percent": round(snapshot.process_percent, 1) + }, + "gc_objects": snapshot.gc_objects, + "status": self._get_memory_status(snapshot.system_percent / 100.0) + } + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "獲取當前內存信息"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"獲取內存信息失敗 [錯誤ID: {error_id}]: {e}") + return {} + + def get_memory_stats(self) -> MemoryStats: + """獲取內存統計數據""" + if not self.snapshots: + return MemoryStats( + monitoring_duration=0.0, + snapshots_count=0, + average_system_usage=0.0, + peak_system_usage=0.0, + average_process_usage=0.0, + peak_process_usage=0.0, + alerts_count=0, + cleanup_triggers=0, + memory_trend="unknown" + ) + + # 計算統計數據 + system_usages = [s.system_percent for s in self.snapshots] + process_usages = [s.process_percent for s in self.snapshots] + + duration = 0.0 + if self.start_time: + duration = (datetime.now() - self.start_time).total_seconds() + + return MemoryStats( + monitoring_duration=duration, + snapshots_count=len(self.snapshots), + average_system_usage=sum(system_usages) / len(system_usages), + peak_system_usage=max(system_usages), + average_process_usage=sum(process_usages) / len(process_usages), + peak_process_usage=max(process_usages), + alerts_count=len(self.alerts), + cleanup_triggers=self.cleanup_triggers_count, + memory_trend=self._analyze_memory_trend() + ) + + def get_recent_alerts(self, limit: int = 10) -> List[MemoryAlert]: + """獲取最近的警告""" + return self.alerts[-limit:] if self.alerts else [] + + def _get_memory_status(self, usage_percent: float) -> str: + """獲取內存狀態描述""" + if usage_percent >= self.emergency_threshold: + return "emergency" + elif usage_percent >= self.critical_threshold: + return "critical" + elif usage_percent >= self.warning_threshold: + return "warning" + else: + return "normal" + + def _analyze_memory_trend(self) -> str: + """分析內存使用趨勢""" + if len(self.snapshots) < 10: + return "insufficient_data" + + # 取最近的快照進行趨勢分析 + recent_snapshots = list(self.snapshots)[-10:] + usages = [s.system_percent for s in recent_snapshots] + + # 簡單的線性趨勢分析 + first_half = usages[:5] + second_half = usages[5:] + + avg_first = sum(first_half) / len(first_half) + avg_second = sum(second_half) / len(second_half) + + diff = avg_second - avg_first + + if abs(diff) < 2.0: # 變化小於 2% + return "stable" + elif diff > 0: + return "increasing" + else: + return "decreasing" + + def force_cleanup(self): + """手動觸發清理操作""" + debug_log("手動觸發內存清理") + self._trigger_cleanup() + + def force_emergency_cleanup(self): + """手動觸發緊急清理操作""" + debug_log("手動觸發緊急內存清理") + self._trigger_emergency_cleanup() + + def reset_stats(self): + """重置統計數據""" + self.snapshots.clear() + self.alerts.clear() + self.cleanup_triggers_count = 0 + self.start_time = datetime.now() if self.is_monitoring else None + debug_log("內存監控統計數據已重置") + + def export_memory_data(self) -> Dict[str, Any]: + """導出內存數據""" + return { + "config": { + "warning_threshold": self.warning_threshold, + "critical_threshold": self.critical_threshold, + "emergency_threshold": self.emergency_threshold, + "monitoring_interval": self.monitoring_interval + }, + "current_info": self.get_current_memory_info(), + "stats": self.get_memory_stats().__dict__, + "recent_alerts": [ + { + "level": alert.level, + "message": alert.message, + "timestamp": alert.timestamp.isoformat(), + "memory_percent": alert.memory_percent, + "recommended_action": alert.recommended_action + } + for alert in self.get_recent_alerts() + ], + "is_monitoring": self.is_monitoring + } + + +# 全域內存監控器實例 +_memory_monitor: Optional[MemoryMonitor] = None +_monitor_lock = threading.Lock() + + +def get_memory_monitor() -> MemoryMonitor: + """獲取全域內存監控器實例""" + global _memory_monitor + if _memory_monitor is None: + with _monitor_lock: + if _memory_monitor is None: + _memory_monitor = MemoryMonitor() + return _memory_monitor diff --git a/src/mcp_feedback_enhanced/utils/resource_manager.py b/src/mcp_feedback_enhanced/utils/resource_manager.py index 205c180..8bd200c 100644 --- a/src/mcp_feedback_enhanced/utils/resource_manager.py +++ b/src/mcp_feedback_enhanced/utils/resource_manager.py @@ -82,9 +82,71 @@ class ResourceManager: # 啟動自動清理 self._start_auto_cleanup() - + + # 集成內存監控 + self._setup_memory_monitoring() + debug_log("ResourceManager 初始化完成") - + + def _setup_memory_monitoring(self): + """設置內存監控集成""" + try: + # 延遲導入避免循環依賴 + from .memory_monitor import get_memory_monitor + + self.memory_monitor = get_memory_monitor() + + # 註冊清理回調 + self.memory_monitor.add_cleanup_callback(self._memory_triggered_cleanup) + + # 啟動內存監控 + if self.memory_monitor.start_monitoring(): + debug_log("內存監控已集成到資源管理器") + else: + debug_log("內存監控啟動失敗") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "設置內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"設置內存監控失敗 [錯誤ID: {error_id}]: {e}") + + def _memory_triggered_cleanup(self, force: bool = False): + """內存監控觸發的清理操作""" + debug_log(f"內存監控觸發清理操作 (force={force})") + + try: + # 清理臨時文件 + cleaned_files = self.cleanup_temp_files() + + # 清理臨時目錄 + cleaned_dirs = self.cleanup_temp_dirs() + + # 清理文件句柄 + cleaned_handles = self.cleanup_file_handles() + + # 如果是強制清理,也清理進程 + cleaned_processes = 0 + if force: + cleaned_processes = self.cleanup_processes(force=True) + + debug_log(f"內存觸發清理完成: 文件={cleaned_files}, 目錄={cleaned_dirs}, " + f"句柄={cleaned_handles}, 進程={cleaned_processes}") + + # 更新統計 + self.stats["cleanup_runs"] += 1 + self.stats["last_cleanup"] = time.time() + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存觸發清理", "force": force}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存觸發清理失敗 [錯誤ID: {error_id}]: {e}") + def create_temp_file( self, suffix: str = "", @@ -620,6 +682,22 @@ class ResourceManager: "temp_file_max_age": self.temp_file_max_age }) + # 添加內存監控統計 + try: + if hasattr(self, 'memory_monitor') and self.memory_monitor: + memory_info = self.memory_monitor.get_current_memory_info() + memory_stats = self.memory_monitor.get_memory_stats() + + current_stats.update({ + "memory_monitoring_enabled": self.memory_monitor.is_monitoring, + "current_memory_usage": memory_info.get("system", {}).get("usage_percent", 0), + "memory_status": memory_info.get("status", "unknown"), + "memory_cleanup_triggers": memory_stats.cleanup_triggers, + "memory_alerts_count": memory_stats.alerts_count + }) + except Exception as e: + debug_log(f"獲取內存統計失敗: {e}") + return current_stats def get_detailed_info(self) -> Dict[str, Any]: diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index a837aed..05b50fc 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -32,6 +32,7 @@ from .utils import find_free_port, get_browser_opener from .utils.port_manager import PortManager from .utils.compression_config import get_compression_manager from ..utils.error_handler import ErrorHandler, ErrorType +from ..utils.memory_monitor import get_memory_monitor from ..debug import web_debug_log as debug_log from ..i18n import get_i18n_manager @@ -71,6 +72,9 @@ class WebUIManager: # 設置壓縮和緩存中間件 self._setup_compression_middleware() + # 設置內存監控 + self._setup_memory_monitoring() + # 重構:使用單一活躍會話而非會話字典 self.current_session: Optional[WebFeedbackSession] = None self.sessions: Dict[str, WebFeedbackSession] = {} # 保留用於向後兼容 @@ -136,6 +140,33 @@ class WebUIManager: debug_log("壓縮和緩存中間件設置完成") + def _setup_memory_monitoring(self): + """設置內存監控""" + try: + self.memory_monitor = get_memory_monitor() + + # 添加 Web 應用特定的警告回調 + def web_memory_alert(alert): + debug_log(f"Web UI 內存警告 [{alert.level}]: {alert.message}") + # 可以在這裡添加更多 Web 特定的處理邏輯 + # 例如:通過 WebSocket 通知前端、記錄到特定日誌等 + + self.memory_monitor.add_alert_callback(web_memory_alert) + + # 確保內存監控已啟動(ResourceManager 可能已經啟動了) + if not self.memory_monitor.is_monitoring: + self.memory_monitor.start_monitoring() + + debug_log("Web UI 內存監控設置完成") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "設置 Web UI 內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"設置 Web UI 內存監控失敗 [錯誤ID: {error_id}]: {e}") + def _setup_static_files(self): """設置靜態文件服務""" # Web UI 靜態文件 diff --git a/tests/test_memory_monitor.py b/tests/test_memory_monitor.py new file mode 100644 index 0000000..d484c8e --- /dev/null +++ b/tests/test_memory_monitor.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +內存監控系統測試 +================ + +測試集成式內存監控系統的功能,包括: +- 內存監控準確性 +- 警告機制 +- 清理觸發 +- 統計和分析功能 +""" + +import pytest +import time +import threading +from unittest.mock import Mock, patch, MagicMock +from datetime import datetime, timedelta + +from src.mcp_feedback_enhanced.utils.memory_monitor import ( + MemoryMonitor, MemorySnapshot, MemoryAlert, MemoryStats, + get_memory_monitor +) + + +class TestMemorySnapshot: + """測試內存快照數據類""" + + def test_memory_snapshot_creation(self): + """測試內存快照創建""" + snapshot = MemorySnapshot( + timestamp=datetime.now(), + system_total=8 * 1024**3, # 8GB + system_available=4 * 1024**3, # 4GB + system_used=4 * 1024**3, # 4GB + system_percent=50.0, + process_rss=100 * 1024**2, # 100MB + process_vms=200 * 1024**2, # 200MB + process_percent=1.25, + gc_objects=10000 + ) + + assert snapshot.system_total == 8 * 1024**3 + assert snapshot.system_percent == 50.0 + assert snapshot.process_rss == 100 * 1024**2 + assert snapshot.gc_objects == 10000 + + +class TestMemoryAlert: + """測試內存警告數據類""" + + def test_memory_alert_creation(self): + """測試內存警告創建""" + alert = MemoryAlert( + level="warning", + message="內存使用率較高: 85.0%", + timestamp=datetime.now(), + memory_percent=85.0, + recommended_action="考慮執行輕量級清理" + ) + + assert alert.level == "warning" + assert alert.memory_percent == 85.0 + assert "85.0%" in alert.message + + +class TestMemoryMonitor: + """測試內存監控器""" + + def test_monitor_initialization(self): + """測試監控器初始化""" + monitor = MemoryMonitor( + warning_threshold=0.7, + critical_threshold=0.85, + emergency_threshold=0.95, + monitoring_interval=10 + ) + + assert monitor.warning_threshold == 0.7 + assert monitor.critical_threshold == 0.85 + assert monitor.emergency_threshold == 0.95 + assert monitor.monitoring_interval == 10 + assert not monitor.is_monitoring + assert len(monitor.snapshots) == 0 + assert len(monitor.alerts) == 0 + + @patch('src.mcp_feedback_enhanced.utils.memory_monitor.psutil') + def test_collect_memory_snapshot(self, mock_psutil): + """測試內存快照收集""" + # 模擬 psutil 返回值 + mock_virtual_memory = Mock() + mock_virtual_memory.total = 8 * 1024**3 + mock_virtual_memory.available = 4 * 1024**3 + mock_virtual_memory.used = 4 * 1024**3 + mock_virtual_memory.percent = 50.0 + + mock_memory_info = Mock() + mock_memory_info.rss = 100 * 1024**2 + mock_memory_info.vms = 200 * 1024**2 + + mock_process = Mock() + mock_process.memory_info.return_value = mock_memory_info + mock_process.memory_percent.return_value = 1.25 + + mock_psutil.virtual_memory.return_value = mock_virtual_memory + mock_psutil.Process.return_value = mock_process + + monitor = MemoryMonitor() + snapshot = monitor._collect_memory_snapshot() + + assert snapshot.system_total == 8 * 1024**3 + assert snapshot.system_percent == 50.0 + assert snapshot.process_rss == 100 * 1024**2 + assert snapshot.process_percent == 1.25 + + def test_memory_status_classification(self): + """測試內存狀態分類""" + monitor = MemoryMonitor( + warning_threshold=0.8, + critical_threshold=0.9, + emergency_threshold=0.95 + ) + + assert monitor._get_memory_status(0.5) == "normal" + assert monitor._get_memory_status(0.85) == "warning" + assert monitor._get_memory_status(0.92) == "critical" + assert monitor._get_memory_status(0.97) == "emergency" + + def test_callback_management(self): + """測試回調函數管理""" + monitor = MemoryMonitor() + + cleanup_callback = Mock() + alert_callback = Mock() + + # 添加回調 + monitor.add_cleanup_callback(cleanup_callback) + monitor.add_alert_callback(alert_callback) + + assert cleanup_callback in monitor.cleanup_callbacks + assert alert_callback in monitor.alert_callbacks + + # 移除回調 + monitor.remove_cleanup_callback(cleanup_callback) + monitor.remove_alert_callback(alert_callback) + + assert cleanup_callback not in monitor.cleanup_callbacks + assert alert_callback not in monitor.alert_callbacks + + @patch('src.mcp_feedback_enhanced.utils.memory_monitor.gc') + def test_cleanup_triggering(self, mock_gc): + """測試清理觸發""" + monitor = MemoryMonitor() + cleanup_callback = Mock() + monitor.add_cleanup_callback(cleanup_callback) + + mock_gc.collect.return_value = 42 + + # 測試普通清理 + monitor._trigger_cleanup() + + assert monitor.cleanup_triggers_count == 1 + cleanup_callback.assert_called_once() + mock_gc.collect.assert_called() + + # 測試緊急清理 + cleanup_callback.reset_mock() + mock_gc.collect.reset_mock() + + monitor._trigger_emergency_cleanup() + + # 緊急清理會調用多次垃圾回收 + assert mock_gc.collect.call_count == 3 + + @patch('src.mcp_feedback_enhanced.utils.memory_monitor.psutil') + def test_memory_usage_checking(self, mock_psutil): + """測試內存使用檢查和警告觸發""" + monitor = MemoryMonitor( + warning_threshold=0.8, + critical_threshold=0.9, + emergency_threshold=0.95 + ) + + alert_callback = Mock() + cleanup_callback = Mock() + monitor.add_alert_callback(alert_callback) + monitor.add_cleanup_callback(cleanup_callback) + + # 模擬不同的內存使用情況 + test_cases = [ + (75.0, "normal", 0, 0), # 正常情況 + (85.0, "warning", 1, 0), # 警告情況 + (92.0, "critical", 1, 1), # 危險情況 + (97.0, "emergency", 1, 1), # 緊急情況 + ] + + for memory_percent, expected_status, expected_alerts, expected_cleanups in test_cases: + # 重置計數器 + alert_callback.reset_mock() + cleanup_callback.reset_mock() + monitor.alerts.clear() + monitor.cleanup_triggers_count = 0 + + # 創建模擬快照 + snapshot = MemorySnapshot( + timestamp=datetime.now(), + system_total=8 * 1024**3, + system_available=int(8 * 1024**3 * (100 - memory_percent) / 100), + system_used=int(8 * 1024**3 * memory_percent / 100), + system_percent=memory_percent, + process_rss=100 * 1024**2, + process_vms=200 * 1024**2, + process_percent=1.25, + gc_objects=10000 + ) + + # 檢查內存使用 + monitor._check_memory_usage(snapshot) + + # 驗證結果 + assert monitor._get_memory_status(memory_percent / 100.0) == expected_status + + if expected_alerts > 0: + assert len(monitor.alerts) == expected_alerts + assert alert_callback.call_count == expected_alerts + + if expected_cleanups > 0: + assert cleanup_callback.call_count == expected_cleanups + + def test_memory_trend_analysis(self): + """測試內存趨勢分析""" + monitor = MemoryMonitor() + + # 測試數據不足的情況 + assert monitor._analyze_memory_trend() == "insufficient_data" + + # 添加穩定趨勢的快照 + base_time = datetime.now() + for i in range(10): + snapshot = MemorySnapshot( + timestamp=base_time + timedelta(seconds=i * 30), + system_total=8 * 1024**3, + system_available=4 * 1024**3, + system_used=4 * 1024**3, + system_percent=50.0 + (i % 2), # 輕微波動 + process_rss=100 * 1024**2, + process_vms=200 * 1024**2, + process_percent=1.25, + gc_objects=10000 + ) + monitor.snapshots.append(snapshot) + + assert monitor._analyze_memory_trend() == "stable" + + # 清空並添加遞增趨勢的快照 + monitor.snapshots.clear() + for i in range(10): + snapshot = MemorySnapshot( + timestamp=base_time + timedelta(seconds=i * 30), + system_total=8 * 1024**3, + system_available=4 * 1024**3, + system_used=4 * 1024**3, + system_percent=50.0 + i * 2, # 遞增趨勢 + process_rss=100 * 1024**2, + process_vms=200 * 1024**2, + process_percent=1.25, + gc_objects=10000 + ) + monitor.snapshots.append(snapshot) + + assert monitor._analyze_memory_trend() == "increasing" + + @patch('src.mcp_feedback_enhanced.utils.memory_monitor.psutil') + def test_get_current_memory_info(self, mock_psutil): + """測試獲取當前內存信息""" + # 模擬 psutil 返回值 + mock_virtual_memory = Mock() + mock_virtual_memory.total = 8 * 1024**3 + mock_virtual_memory.available = 4 * 1024**3 + mock_virtual_memory.used = 4 * 1024**3 + mock_virtual_memory.percent = 50.0 + + mock_memory_info = Mock() + mock_memory_info.rss = 100 * 1024**2 + mock_memory_info.vms = 200 * 1024**2 + + mock_process = Mock() + mock_process.memory_info.return_value = mock_memory_info + mock_process.memory_percent.return_value = 1.25 + + mock_psutil.virtual_memory.return_value = mock_virtual_memory + mock_psutil.Process.return_value = mock_process + + monitor = MemoryMonitor() + info = monitor.get_current_memory_info() + + assert "system" in info + assert "process" in info + assert info["system"]["total_gb"] == 8.0 + assert info["system"]["usage_percent"] == 50.0 + assert info["process"]["rss_mb"] == 100.0 + assert info["status"] == "normal" + + def test_memory_stats_calculation(self): + """測試內存統計計算""" + monitor = MemoryMonitor() + monitor.start_time = datetime.now() - timedelta(minutes=5) + + # 添加一些測試快照 + base_time = datetime.now() + for i in range(5): + snapshot = MemorySnapshot( + timestamp=base_time + timedelta(seconds=i * 30), + system_total=8 * 1024**3, + system_available=4 * 1024**3, + system_used=4 * 1024**3, + system_percent=50.0 + i * 5, # 50%, 55%, 60%, 65%, 70% + process_rss=100 * 1024**2, + process_vms=200 * 1024**2, + process_percent=1.0 + i * 0.2, # 1.0%, 1.2%, 1.4%, 1.6%, 1.8% + gc_objects=10000 + ) + monitor.snapshots.append(snapshot) + + # 添加一些警告 + monitor.alerts.append(MemoryAlert( + level="warning", + message="Test warning", + timestamp=datetime.now(), + memory_percent=85.0, + recommended_action="Test action" + )) + + monitor.cleanup_triggers_count = 2 + + stats = monitor.get_memory_stats() + + assert stats.snapshots_count == 5 + assert stats.average_system_usage == 60.0 # (50+55+60+65+70)/5 + assert stats.peak_system_usage == 70.0 + assert stats.average_process_usage == 1.4 # (1.0+1.2+1.4+1.6+1.8)/5 + assert stats.peak_process_usage == 1.8 + assert stats.alerts_count == 1 + assert stats.cleanup_triggers == 2 + assert stats.monitoring_duration > 0 + + def test_export_memory_data(self): + """測試內存數據導出""" + monitor = MemoryMonitor() + + # 添加一些測試數據 + monitor.alerts.append(MemoryAlert( + level="warning", + message="Test warning", + timestamp=datetime.now(), + memory_percent=85.0, + recommended_action="Test action" + )) + + with patch.object(monitor, 'get_current_memory_info') as mock_info: + mock_info.return_value = { + "system": {"usage_percent": 75.0}, + "status": "warning" + } + + exported_data = monitor.export_memory_data() + + assert "config" in exported_data + assert "current_info" in exported_data + assert "stats" in exported_data + assert "recent_alerts" in exported_data + assert "is_monitoring" in exported_data + + assert exported_data["config"]["warning_threshold"] == 0.8 + assert len(exported_data["recent_alerts"]) == 1 + + +def test_global_memory_monitor_singleton(): + """測試全域內存監控器單例模式""" + monitor1 = get_memory_monitor() + monitor2 = get_memory_monitor() + + assert monitor1 is monitor2 + assert isinstance(monitor1, MemoryMonitor) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From b9d781f147234fb39ab9d98011689b2418a6e1f3 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 02:54:46 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E6=9C=83?= =?UTF-8?q?=E8=A9=B1=E6=B8=85=E7=90=86=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E6=95=B4=E5=90=88=E8=87=AA=E5=8B=95=E6=B8=85=E7=90=86=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E3=80=81=E7=B5=B1=E8=A8=88=E8=88=87=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E7=9B=A3=E6=8E=A7=EF=BC=8C=E6=8F=90=E5=8D=87=E7=B3=BB=E7=B5=B1?= =?UTF-8?q?=E8=B3=87=E6=BA=90=E7=AE=A1=E7=90=86=E6=95=88=E7=8E=87=E3=80=82?= =?UTF-8?q?=E6=93=B4=E5=B1=95=20WebFeedbackSession=20=E5=8F=8A=20SessionCl?= =?UTF-8?q?eanupManager=20=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=81=8E=E6=9C=9F=E8=88=87=E5=85=A7=E5=AD=98=E5=A3=93=E5=8A=9B?= =?UTF-8?q?=E6=B8=85=E7=90=86=EF=BC=8C=E4=B8=A6=E6=96=B0=E5=A2=9E=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6=E6=A8=A1=E7=B5=84=E4=BB=A5=E7=A2=BA=E4=BF=9D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=AD=A3=E7=A2=BA=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/web/main.py | 219 +++++++- .../web/models/__init__.py | 6 +- .../web/models/feedback_session.py | 451 ++++++++++++++-- .../web/utils/session_cleanup_manager.py | 504 ++++++++++++++++++ tests/test_session_cleanup.py | 366 +++++++++++++ 5 files changed, 1481 insertions(+), 65 deletions(-) create mode 100644 src/mcp_feedback_enhanced/web/utils/session_cleanup_manager.py create mode 100644 tests/test_session_cleanup.py diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 05b50fc..f4aa907 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -17,7 +17,8 @@ import threading import time import webbrowser from pathlib import Path -from typing import Dict, Optional +from typing import Dict, Optional, List +from datetime import datetime import uuid from fastapi import FastAPI, Request, Response @@ -26,7 +27,7 @@ from fastapi.templating import Jinja2Templates from fastapi.middleware.gzip import GZipMiddleware import uvicorn -from .models import WebFeedbackSession, FeedbackResult +from .models import WebFeedbackSession, FeedbackResult, CleanupReason, SessionStatus from .routes import setup_routes from .utils import find_free_port, get_browser_opener from .utils.port_manager import PortManager @@ -85,6 +86,17 @@ class WebUIManager: # 會話更新通知標記 self._pending_session_update = False + # 會話清理統計 + self.cleanup_stats = { + "total_cleanups": 0, + "expired_cleanups": 0, + "memory_pressure_cleanups": 0, + "manual_cleanups": 0, + "last_cleanup_time": None, + "total_cleanup_duration": 0.0, + "sessions_cleaned": 0 + } + self.server_thread = None self.server_process = None self.i18n = get_i18n_manager() @@ -148,16 +160,46 @@ class WebUIManager: # 添加 Web 應用特定的警告回調 def web_memory_alert(alert): debug_log(f"Web UI 內存警告 [{alert.level}]: {alert.message}") - # 可以在這裡添加更多 Web 特定的處理邏輯 - # 例如:通過 WebSocket 通知前端、記錄到特定日誌等 + + # 根據警告級別觸發不同的清理策略 + if alert.level == "critical": + # 危險級別:清理過期會話 + cleaned = self.cleanup_expired_sessions() + debug_log(f"內存危險警告觸發,清理了 {cleaned} 個過期會話") + elif alert.level == "emergency": + # 緊急級別:強制清理會話 + cleaned = self.cleanup_sessions_by_memory_pressure(force=True) + debug_log(f"內存緊急警告觸發,強制清理了 {cleaned} 個會話") self.memory_monitor.add_alert_callback(web_memory_alert) + # 添加會話清理回調到內存監控 + def session_cleanup_callback(force: bool = False): + """內存監控觸發的會話清理回調""" + try: + if force: + # 強制清理:包括內存壓力清理 + cleaned = self.cleanup_sessions_by_memory_pressure(force=True) + debug_log(f"內存監控強制清理了 {cleaned} 個會話") + else: + # 常規清理:只清理過期會話 + cleaned = self.cleanup_expired_sessions() + debug_log(f"內存監控清理了 {cleaned} 個過期會話") + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存監控會話清理", "force": force}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存監控會話清理失敗 [錯誤ID: {error_id}]: {e}") + + self.memory_monitor.add_cleanup_callback(session_cleanup_callback) + # 確保內存監控已啟動(ResourceManager 可能已經啟動了) if not self.memory_monitor.is_monitoring: self.memory_monitor.start_monitoring() - debug_log("Web UI 內存監控設置完成") + debug_log("Web UI 內存監控設置完成,已集成會話清理回調") except Exception as e: error_id = ErrorHandler.log_error_with_context( @@ -563,13 +605,176 @@ class WebUIManager: """獲取伺服器 URL""" return f"http://{self.host}:{self.port}" + def cleanup_expired_sessions(self) -> int: + """清理過期會話""" + cleanup_start_time = time.time() + expired_sessions = [] + + # 掃描過期會話 + for session_id, session in self.sessions.items(): + if session.is_expired(): + expired_sessions.append(session_id) + + # 批量清理過期會話 + cleaned_count = 0 + for session_id in expired_sessions: + try: + session = self.sessions.get(session_id) + if session: + # 使用增強清理方法 + session._cleanup_sync_enhanced(CleanupReason.EXPIRED) + del self.sessions[session_id] + cleaned_count += 1 + + # 如果清理的是當前活躍會話,清空當前會話 + if self.current_session and self.current_session.session_id == session_id: + self.current_session = None + debug_log("清空過期的當前活躍會話") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": session_id, "operation": "清理過期會話"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"清理過期會話 {session_id} 失敗 [錯誤ID: {error_id}]: {e}") + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "expired_cleanups": self.cleanup_stats["expired_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + cleaned_count + }) + + if cleaned_count > 0: + debug_log(f"清理了 {cleaned_count} 個過期會話,耗時: {cleanup_duration:.2f}秒") + + return cleaned_count + + def cleanup_sessions_by_memory_pressure(self, force: bool = False) -> int: + """根據內存壓力清理會話""" + cleanup_start_time = time.time() + sessions_to_clean = [] + + # 根據優先級選擇要清理的會話 + # 優先級:已完成 > 已提交反饋 > 錯誤狀態 > 空閒時間最長 + for session_id, session in self.sessions.items(): + # 跳過當前活躍會話(除非強制清理) + if not force and self.current_session and session.session_id == self.current_session.session_id: + continue + + # 優先清理已完成或錯誤狀態的會話 + if session.status in [SessionStatus.COMPLETED, SessionStatus.ERROR, SessionStatus.TIMEOUT]: + sessions_to_clean.append((session_id, session, 1)) # 高優先級 + elif session.status == SessionStatus.FEEDBACK_SUBMITTED: + # 已提交反饋但空閒時間較長的會話 + if session.get_idle_time() > 300: # 5分鐘空閒 + sessions_to_clean.append((session_id, session, 2)) # 中優先級 + elif session.get_idle_time() > 600: # 10分鐘空閒 + sessions_to_clean.append((session_id, session, 3)) # 低優先級 + + # 按優先級排序 + sessions_to_clean.sort(key=lambda x: x[2]) + + # 清理會話(限制數量避免過度清理) + max_cleanup = min(len(sessions_to_clean), 5 if not force else len(sessions_to_clean)) + cleaned_count = 0 + + for i in range(max_cleanup): + session_id, session, priority = sessions_to_clean[i] + try: + # 使用增強清理方法 + session._cleanup_sync_enhanced(CleanupReason.MEMORY_PRESSURE) + del self.sessions[session_id] + cleaned_count += 1 + + # 如果清理的是當前活躍會話,清空當前會話 + if self.current_session and self.current_session.session_id == session_id: + self.current_session = None + debug_log("因內存壓力清空當前活躍會話") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": session_id, "operation": "內存壓力清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存壓力清理會話 {session_id} 失敗 [錯誤ID: {error_id}]: {e}") + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "memory_pressure_cleanups": self.cleanup_stats["memory_pressure_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + cleaned_count + }) + + if cleaned_count > 0: + debug_log(f"因內存壓力清理了 {cleaned_count} 個會話,耗時: {cleanup_duration:.2f}秒") + + return cleaned_count + + def get_session_cleanup_stats(self) -> dict: + """獲取會話清理統計""" + stats = self.cleanup_stats.copy() + stats.update({ + "active_sessions": len(self.sessions), + "current_session_id": self.current_session.session_id if self.current_session else None, + "expired_sessions": sum(1 for s in self.sessions.values() if s.is_expired()), + "idle_sessions": sum(1 for s in self.sessions.values() if s.get_idle_time() > 300), + "memory_usage_mb": 0 # 將在下面計算 + }) + + # 計算內存使用(如果可能) + try: + import psutil + process = psutil.Process() + stats["memory_usage_mb"] = round(process.memory_info().rss / (1024 * 1024), 2) + except: + pass + + return stats + + def _scan_expired_sessions(self) -> List[str]: + """掃描過期會話ID列表""" + expired_sessions = [] + for session_id, session in self.sessions.items(): + if session.is_expired(): + expired_sessions.append(session_id) + return expired_sessions + def stop(self): """停止 Web UI 服務""" # 清理所有會話 + cleanup_start_time = time.time() + session_count = len(self.sessions) + for session in list(self.sessions.values()): - session.cleanup() + try: + session._cleanup_sync_enhanced(CleanupReason.SHUTDOWN) + except Exception as e: + debug_log(f"停止服務時清理會話失敗: {e}") + self.sessions.clear() - + self.current_session = None + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "manual_cleanups": self.cleanup_stats["manual_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + session_count + }) + + debug_log(f"停止服務時清理了 {session_count} 個會話,耗時: {cleanup_duration:.2f}秒") + # 停止伺服器(注意:uvicorn 的 graceful shutdown 需要額外處理) if self.server_thread and self.server_thread.is_alive(): debug_log("正在停止 Web UI 服務") diff --git a/src/mcp_feedback_enhanced/web/models/__init__.py b/src/mcp_feedback_enhanced/web/models/__init__.py index d06dd01..36e7108 100644 --- a/src/mcp_feedback_enhanced/web/models/__init__.py +++ b/src/mcp_feedback_enhanced/web/models/__init__.py @@ -7,10 +7,12 @@ Web UI 資料模型模組 定義 Web UI 相關的資料結構和型別。 """ -from .feedback_session import WebFeedbackSession +from .feedback_session import WebFeedbackSession, SessionStatus, CleanupReason from .feedback_result import FeedbackResult __all__ = [ 'WebFeedbackSession', + 'SessionStatus', + 'CleanupReason', 'FeedbackResult' -] \ No newline at end of file +] \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index 6089f63..05c8d6f 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -11,14 +11,17 @@ import asyncio import base64 import subprocess import threading +import time +from datetime import datetime, timedelta from enum import Enum from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Callable from fastapi import WebSocket from ...debug import web_debug_log as debug_log from ...utils.resource_manager import get_resource_manager, register_process +from ...utils.error_handler import ErrorHandler, ErrorType class SessionStatus(Enum): @@ -29,6 +32,17 @@ class SessionStatus(Enum): COMPLETED = "completed" # 已完成 TIMEOUT = "timeout" # 超時 ERROR = "error" # 錯誤 + EXPIRED = "expired" # 已過期 + + +class CleanupReason(Enum): + """清理原因枚舉""" + TIMEOUT = "timeout" # 超時清理 + EXPIRED = "expired" # 過期清理 + MEMORY_PRESSURE = "memory_pressure" # 內存壓力清理 + MANUAL = "manual" # 手動清理 + ERROR = "error" # 錯誤清理 + SHUTDOWN = "shutdown" # 系統關閉清理 # 常數定義 MAX_IMAGE_SIZE = 1 * 1024 * 1024 # 1MB 圖片大小限制 @@ -39,7 +53,8 @@ TEMP_DIR = Path.home() / ".cache" / "interactive-feedback-mcp-web" class WebFeedbackSession: """Web 回饋會話管理""" - def __init__(self, session_id: str, project_directory: str, summary: str): + def __init__(self, session_id: str, project_directory: str, summary: str, + auto_cleanup_delay: int = 3600, max_idle_time: int = 1800): self.session_id = session_id self.project_directory = project_directory self.summary = summary @@ -55,21 +70,49 @@ class WebFeedbackSession: # 新增:會話狀態管理 self.status = SessionStatus.WAITING self.status_message = "等待用戶回饋" - self.created_at = asyncio.get_event_loop().time() + # 統一使用 time.time() 以避免時間基準不一致 + self.created_at = time.time() self.last_activity = self.created_at + # 新增:自動清理配置 + self.auto_cleanup_delay = auto_cleanup_delay # 自動清理延遲時間(秒) + self.max_idle_time = max_idle_time # 最大空閒時間(秒) + self.cleanup_timer: Optional[threading.Timer] = None + self.cleanup_callbacks: List[Callable] = [] # 清理回調函數列表 + + # 新增:清理統計 + self.cleanup_stats = { + "cleanup_count": 0, + "last_cleanup_time": None, + "cleanup_reason": None, + "cleanup_duration": 0.0, + "memory_freed": 0, + "resources_cleaned": 0 + } + # 確保臨時目錄存在 TEMP_DIR.mkdir(parents=True, exist_ok=True) # 獲取資源管理器實例 self.resource_manager = get_resource_manager() + # 啟動自動清理定時器 + self._schedule_auto_cleanup() + + debug_log(f"會話 {self.session_id} 初始化完成,自動清理延遲: {auto_cleanup_delay}秒,最大空閒: {max_idle_time}秒") + def update_status(self, status: SessionStatus, message: str = None): """更新會話狀態""" self.status = status if message: self.status_message = message - self.last_activity = asyncio.get_event_loop().time() + # 統一使用 time.time() + self.last_activity = time.time() + + # 如果會話變為活躍狀態,重置清理定時器 + if status in [SessionStatus.ACTIVE, SessionStatus.FEEDBACK_SUBMITTED]: + self._schedule_auto_cleanup() + debug_log(f"會話 {self.session_id} 狀態更新: {status.value} - {self.status_message}") def get_status_info(self) -> dict: @@ -90,6 +133,117 @@ class WebFeedbackSession: """檢查會話是否活躍""" return self.status in [SessionStatus.WAITING, SessionStatus.ACTIVE, SessionStatus.FEEDBACK_SUBMITTED] + def is_expired(self) -> bool: + """檢查會話是否已過期""" + # 統一使用 time.time() + current_time = time.time() + + # 檢查是否超過最大空閒時間 + idle_time = current_time - self.last_activity + if idle_time > self.max_idle_time: + debug_log(f"會話 {self.session_id} 空閒時間過長: {idle_time:.1f}秒 > {self.max_idle_time}秒") + return True + + # 檢查是否處於已過期狀態 + if self.status == SessionStatus.EXPIRED: + return True + + # 檢查是否處於錯誤或超時狀態且超過一定時間 + if self.status in [SessionStatus.ERROR, SessionStatus.TIMEOUT]: + error_time = current_time - self.last_activity + if error_time > 300: # 錯誤狀態超過5分鐘視為過期 + debug_log(f"會話 {self.session_id} 錯誤狀態時間過長: {error_time:.1f}秒") + return True + + return False + + def get_age(self) -> float: + """獲取會話年齡(秒)""" + current_time = time.time() + return current_time - self.created_at + + def get_idle_time(self) -> float: + """獲取會話空閒時間(秒)""" + current_time = time.time() + return current_time - self.last_activity + + def _schedule_auto_cleanup(self): + """安排自動清理定時器""" + if self.cleanup_timer: + self.cleanup_timer.cancel() + + def auto_cleanup(): + """自動清理回調""" + try: + if not self._cleanup_done and self.is_expired(): + debug_log(f"會話 {self.session_id} 觸發自動清理(過期)") + # 使用異步方式執行清理 + import asyncio + try: + loop = asyncio.get_event_loop() + loop.create_task(self._cleanup_resources_enhanced(CleanupReason.EXPIRED)) + except RuntimeError: + # 如果沒有事件循環,使用同步清理 + self._cleanup_sync_enhanced(CleanupReason.EXPIRED) + else: + # 如果還沒過期,重新安排定時器 + self._schedule_auto_cleanup() + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": self.session_id, "operation": "自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"自動清理失敗 [錯誤ID: {error_id}]: {e}") + + self.cleanup_timer = threading.Timer(self.auto_cleanup_delay, auto_cleanup) + self.cleanup_timer.daemon = True + self.cleanup_timer.start() + debug_log(f"會話 {self.session_id} 自動清理定時器已設置,{self.auto_cleanup_delay}秒後觸發") + + def extend_cleanup_timer(self, additional_time: int = None): + """延長清理定時器""" + if additional_time is None: + additional_time = self.auto_cleanup_delay + + if self.cleanup_timer: + self.cleanup_timer.cancel() + + self.cleanup_timer = threading.Timer(additional_time, lambda: None) + self.cleanup_timer.daemon = True + self.cleanup_timer.start() + + debug_log(f"會話 {self.session_id} 清理定時器已延長 {additional_time} 秒") + + def add_cleanup_callback(self, callback: Callable): + """添加清理回調函數""" + if callback not in self.cleanup_callbacks: + self.cleanup_callbacks.append(callback) + debug_log(f"會話 {self.session_id} 添加清理回調函數") + + def remove_cleanup_callback(self, callback: Callable): + """移除清理回調函數""" + if callback in self.cleanup_callbacks: + self.cleanup_callbacks.remove(callback) + debug_log(f"會話 {self.session_id} 移除清理回調函數") + + def get_cleanup_stats(self) -> dict: + """獲取清理統計信息""" + stats = self.cleanup_stats.copy() + stats.update({ + "session_id": self.session_id, + "age": self.get_age(), + "idle_time": self.get_idle_time(), + "is_expired": self.is_expired(), + "is_active": self.is_active(), + "status": self.status.value, + "has_websocket": self.websocket is not None, + "has_process": self.process is not None, + "command_logs_count": len(self.command_logs), + "images_count": len(self.images) + }) + return stats + async def wait_for_feedback(self, timeout: int = 600) -> dict: """ 等待用戶回饋,包含圖片,支援超時自動清理 @@ -321,33 +475,72 @@ class WebFeedbackSession: pass async def _cleanup_resources_on_timeout(self): - """超時時清理所有資源""" + """超時時清理所有資源(保持向後兼容)""" + await self._cleanup_resources_enhanced(CleanupReason.TIMEOUT) + + async def _cleanup_resources_enhanced(self, reason: CleanupReason): + """增強的資源清理方法""" if self._cleanup_done: return # 避免重複清理 - + + cleanup_start_time = time.time() self._cleanup_done = True - debug_log(f"開始清理會話 {self.session_id} 的資源...") - + + debug_log(f"開始清理會話 {self.session_id} 的資源,原因: {reason.value}") + + # 更新清理統計 + self.cleanup_stats["cleanup_count"] += 1 + self.cleanup_stats["cleanup_reason"] = reason.value + self.cleanup_stats["last_cleanup_time"] = datetime.now().isoformat() + + resources_cleaned = 0 + memory_before = 0 + try: - # 1. 關閉 WebSocket 連接 + # 記錄清理前的內存使用(如果可能) + try: + import psutil + process = psutil.Process() + memory_before = process.memory_info().rss + except: + pass + + # 1. 取消自動清理定時器 + if self.cleanup_timer: + self.cleanup_timer.cancel() + self.cleanup_timer = None + resources_cleaned += 1 + + # 2. 關閉 WebSocket 連接 if self.websocket: try: - # 先通知前端超時 + # 根據清理原因發送不同的通知消息 + message_map = { + CleanupReason.TIMEOUT: "會話已超時,介面將自動關閉", + CleanupReason.EXPIRED: "會話已過期,介面將自動關閉", + CleanupReason.MEMORY_PRESSURE: "系統內存不足,會話將被清理", + CleanupReason.MANUAL: "會話已被手動清理", + CleanupReason.ERROR: "會話發生錯誤,將被清理", + CleanupReason.SHUTDOWN: "系統正在關閉,會話將被清理" + } + await self.websocket.send_json({ - "type": "session_timeout", - "message": "會話已超時,介面將自動關閉" + "type": "session_cleanup", + "reason": reason.value, + "message": message_map.get(reason, "會話將被清理") }) await asyncio.sleep(0.1) # 給前端一點時間處理消息 # 安全關閉 WebSocket await self._safe_close_websocket() debug_log(f"會話 {self.session_id} WebSocket 已關閉") + resources_cleaned += 1 except Exception as e: debug_log(f"關閉 WebSocket 時發生錯誤: {e}") finally: self.websocket = None - - # 2. 終止正在運行的命令進程 + + # 3. 終止正在運行的命令進程 if self.process: try: self.process.terminate() @@ -357,67 +550,213 @@ class WebFeedbackSession: except subprocess.TimeoutExpired: self.process.kill() debug_log(f"會話 {self.session_id} 命令進程已強制終止") + resources_cleaned += 1 except Exception as e: debug_log(f"終止命令進程時發生錯誤: {e}") finally: self.process = None - - # 3. 設置完成事件(防止其他地方還在等待) + + # 4. 設置完成事件(防止其他地方還在等待) self.feedback_completed.set() - - # 4. 清理臨時數據 + + # 5. 清理臨時數據 + logs_count = len(self.command_logs) + images_count = len(self.images) + self.command_logs.clear() self.images.clear() - - debug_log(f"會話 {self.session_id} 資源清理完成") - + self.settings.clear() + + if logs_count > 0 or images_count > 0: + resources_cleaned += logs_count + images_count + debug_log(f"清理了 {logs_count} 條日誌和 {images_count} 張圖片") + + # 6. 更新會話狀態 + if reason == CleanupReason.EXPIRED: + self.status = SessionStatus.EXPIRED + elif reason == CleanupReason.TIMEOUT: + self.status = SessionStatus.TIMEOUT + elif reason == CleanupReason.ERROR: + self.status = SessionStatus.ERROR + else: + self.status = SessionStatus.COMPLETED + + # 7. 調用清理回調函數 + for callback in self.cleanup_callbacks: + try: + if asyncio.iscoroutinefunction(callback): + await callback(self, reason) + else: + callback(self, reason) + except Exception as e: + debug_log(f"清理回調執行失敗: {e}") + + # 8. 計算清理效果 + cleanup_duration = time.time() - cleanup_start_time + memory_after = 0 + try: + import psutil + process = psutil.Process() + memory_after = process.memory_info().rss + except: + pass + + memory_freed = max(0, memory_before - memory_after) + + # 更新清理統計 + self.cleanup_stats.update({ + "cleanup_duration": cleanup_duration, + "memory_freed": memory_freed, + "resources_cleaned": resources_cleaned + }) + + debug_log(f"會話 {self.session_id} 資源清理完成,耗時: {cleanup_duration:.2f}秒," + f"清理資源: {resources_cleaned}個,釋放內存: {memory_freed}字節") + except Exception as e: - debug_log(f"清理會話 {self.session_id} 資源時發生錯誤: {e}") + error_id = ErrorHandler.log_error_with_context( + e, + context={ + "session_id": self.session_id, + "cleanup_reason": reason.value, + "operation": "增強資源清理" + }, + error_type=ErrorType.SYSTEM + ) + debug_log(f"清理會話 {self.session_id} 資源時發生錯誤 [錯誤ID: {error_id}]: {e}") + + # 即使發生錯誤也要更新統計 + self.cleanup_stats["cleanup_duration"] = time.time() - cleanup_start_time def _cleanup_sync(self): - """同步清理會話資源(但保留 WebSocket 連接)""" - if self._cleanup_done: + """同步清理會話資源(但保留 WebSocket 連接)- 保持向後兼容""" + self._cleanup_sync_enhanced(CleanupReason.MANUAL, preserve_websocket=True) + + def _cleanup_sync_enhanced(self, reason: CleanupReason, preserve_websocket: bool = False): + """增強的同步清理會話資源""" + if self._cleanup_done and not preserve_websocket: return - debug_log(f"同步清理會話 {self.session_id} 資源(保留 WebSocket)...") + cleanup_start_time = time.time() + debug_log(f"同步清理會話 {self.session_id} 資源,原因: {reason.value},保留WebSocket: {preserve_websocket}") - # 只清理進程,不清理 WebSocket 連接 - if self.process: + # 更新清理統計 + self.cleanup_stats["cleanup_count"] += 1 + self.cleanup_stats["cleanup_reason"] = reason.value + self.cleanup_stats["last_cleanup_time"] = datetime.now().isoformat() + + resources_cleaned = 0 + memory_before = 0 + + try: + # 記錄清理前的內存使用 try: - self.process.terminate() - self.process.wait(timeout=5) + import psutil + process = psutil.Process() + memory_before = process.memory_info().rss except: - try: - self.process.kill() - except: - pass - self.process = None + pass - # 清理臨時數據 - self.command_logs.clear() - # 注意:不設置 _cleanup_done = True,因為還需要清理 WebSocket + # 1. 取消自動清理定時器 + if self.cleanup_timer: + self.cleanup_timer.cancel() + self.cleanup_timer = None + resources_cleaned += 1 + + # 2. 清理進程 + if self.process: + try: + self.process.terminate() + self.process.wait(timeout=5) + debug_log(f"會話 {self.session_id} 命令進程已正常終止") + resources_cleaned += 1 + except: + try: + self.process.kill() + debug_log(f"會話 {self.session_id} 命令進程已強制終止") + resources_cleaned += 1 + except: + pass + self.process = None + + # 3. 清理臨時數據 + logs_count = len(self.command_logs) + images_count = len(self.images) + + self.command_logs.clear() + if not preserve_websocket: + self.images.clear() + self.settings.clear() + resources_cleaned += images_count + + resources_cleaned += logs_count + + # 4. 設置完成事件 + if not preserve_websocket: + self.feedback_completed.set() + + # 5. 更新狀態 + if not preserve_websocket: + if reason == CleanupReason.EXPIRED: + self.status = SessionStatus.EXPIRED + elif reason == CleanupReason.TIMEOUT: + self.status = SessionStatus.TIMEOUT + elif reason == CleanupReason.ERROR: + self.status = SessionStatus.ERROR + else: + self.status = SessionStatus.COMPLETED + + self._cleanup_done = True + + # 6. 調用清理回調函數(同步版本) + for callback in self.cleanup_callbacks: + try: + if not asyncio.iscoroutinefunction(callback): + callback(self, reason) + except Exception as e: + debug_log(f"同步清理回調執行失敗: {e}") + + # 7. 計算清理效果 + cleanup_duration = time.time() - cleanup_start_time + memory_after = 0 + try: + import psutil + process = psutil.Process() + memory_after = process.memory_info().rss + except: + pass + + memory_freed = max(0, memory_before - memory_after) + + # 更新清理統計 + self.cleanup_stats.update({ + "cleanup_duration": cleanup_duration, + "memory_freed": memory_freed, + "resources_cleaned": resources_cleaned + }) + + debug_log(f"會話 {self.session_id} 同步清理完成,耗時: {cleanup_duration:.2f}秒," + f"清理資源: {resources_cleaned}個,釋放內存: {memory_freed}字節") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={ + "session_id": self.session_id, + "cleanup_reason": reason.value, + "preserve_websocket": preserve_websocket, + "operation": "同步資源清理" + }, + error_type=ErrorType.SYSTEM + ) + debug_log(f"同步清理會話 {self.session_id} 資源時發生錯誤 [錯誤ID: {error_id}]: {e}") + + # 即使發生錯誤也要更新統計 + self.cleanup_stats["cleanup_duration"] = time.time() - cleanup_start_time def cleanup(self): """同步清理會話資源(保持向後兼容)""" - if self._cleanup_done: - return - - self._cleanup_done = True - debug_log(f"同步清理會話 {self.session_id} 資源...") - - if self.process: - try: - self.process.terminate() - self.process.wait(timeout=5) - except: - try: - self.process.kill() - except: - pass - self.process = None - - # 設置完成事件 - self.feedback_completed.set() + self._cleanup_sync_enhanced(CleanupReason.MANUAL) async def _safe_close_websocket(self): """安全關閉 WebSocket 連接,避免事件循環衝突""" diff --git a/src/mcp_feedback_enhanced/web/utils/session_cleanup_manager.py b/src/mcp_feedback_enhanced/web/utils/session_cleanup_manager.py new file mode 100644 index 0000000..5534db3 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/utils/session_cleanup_manager.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +會話清理管理器 +============== + +統一管理 Web 會話的清理策略、統計和性能監控。 +與內存監控系統深度集成,提供智能清理決策。 +""" + +import time +import threading +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Callable, Any +from dataclasses import dataclass, field +from enum import Enum + +from ...debug import web_debug_log as debug_log +from ...utils.error_handler import ErrorHandler, ErrorType +from ..models.feedback_session import CleanupReason, SessionStatus + + +@dataclass +class CleanupPolicy: + """清理策略配置""" + max_idle_time: int = 1800 # 最大空閒時間(秒) + max_session_age: int = 7200 # 最大會話年齡(秒) + max_sessions: int = 10 # 最大會話數量 + cleanup_interval: int = 300 # 清理間隔(秒) + memory_pressure_threshold: float = 0.8 # 內存壓力閾值 + enable_auto_cleanup: bool = True # 啟用自動清理 + preserve_active_session: bool = True # 保護活躍會話 + + +@dataclass +class CleanupStats: + """清理統計數據""" + total_cleanups: int = 0 + expired_cleanups: int = 0 + memory_pressure_cleanups: int = 0 + manual_cleanups: int = 0 + auto_cleanups: int = 0 + total_sessions_cleaned: int = 0 + total_cleanup_time: float = 0.0 + average_cleanup_time: float = 0.0 + last_cleanup_time: Optional[datetime] = None + cleanup_efficiency: float = 0.0 # 清理效率(清理的會話數/總會話數) + + +class CleanupTrigger(Enum): + """清理觸發器類型""" + AUTO = "auto" # 自動清理 + MEMORY_PRESSURE = "memory_pressure" # 內存壓力 + MANUAL = "manual" # 手動清理 + EXPIRED = "expired" # 過期清理 + CAPACITY = "capacity" # 容量限制 + + +class SessionCleanupManager: + """會話清理管理器""" + + def __init__(self, web_ui_manager, policy: CleanupPolicy = None): + """ + 初始化會話清理管理器 + + Args: + web_ui_manager: WebUIManager 實例 + policy: 清理策略配置 + """ + self.web_ui_manager = web_ui_manager + self.policy = policy or CleanupPolicy() + self.stats = CleanupStats() + + # 清理狀態 + self.is_running = False + self.cleanup_thread: Optional[threading.Thread] = None + self._stop_event = threading.Event() + + # 回調函數 + self.cleanup_callbacks: List[Callable] = [] + self.stats_callbacks: List[Callable] = [] + + # 清理歷史記錄 + self.cleanup_history: List[Dict[str, Any]] = [] + self.max_history = 100 + + debug_log("SessionCleanupManager 初始化完成") + + def start_auto_cleanup(self) -> bool: + """啟動自動清理""" + if not self.policy.enable_auto_cleanup: + debug_log("自動清理已禁用") + return False + + if self.is_running: + debug_log("自動清理已在運行") + return True + + try: + self.is_running = True + self._stop_event.clear() + + self.cleanup_thread = threading.Thread( + target=self._auto_cleanup_loop, + name="SessionCleanupManager", + daemon=True + ) + self.cleanup_thread.start() + + debug_log(f"自動清理已啟動,間隔 {self.policy.cleanup_interval} 秒") + return True + + except Exception as e: + self.is_running = False + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "啟動自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"啟動自動清理失敗 [錯誤ID: {error_id}]: {e}") + return False + + def stop_auto_cleanup(self) -> bool: + """停止自動清理""" + if not self.is_running: + debug_log("自動清理未在運行") + return True + + try: + self.is_running = False + self._stop_event.set() + + if self.cleanup_thread and self.cleanup_thread.is_alive(): + self.cleanup_thread.join(timeout=5) + + debug_log("自動清理已停止") + return True + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "停止自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"停止自動清理失敗 [錯誤ID: {error_id}]: {e}") + return False + + def _auto_cleanup_loop(self): + """自動清理主循環""" + debug_log("自動清理循環開始") + + while not self._stop_event.is_set(): + try: + # 執行清理檢查 + self._perform_auto_cleanup() + + # 等待下次清理 + if self._stop_event.wait(self.policy.cleanup_interval): + break + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "自動清理循環"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"自動清理循環錯誤 [錯誤ID: {error_id}]: {e}") + + # 發生錯誤時等待較短時間後重試 + if self._stop_event.wait(30): + break + + debug_log("自動清理循環結束") + + def _perform_auto_cleanup(self): + """執行自動清理""" + cleanup_start_time = time.time() + cleaned_sessions = 0 + + try: + # 1. 檢查會話數量限制 + if len(self.web_ui_manager.sessions) > self.policy.max_sessions: + cleaned = self._cleanup_by_capacity() + cleaned_sessions += cleaned + debug_log(f"容量限制清理了 {cleaned} 個會話") + + # 2. 清理過期會話 + cleaned = self._cleanup_expired_sessions() + cleaned_sessions += cleaned + + # 3. 清理空閒會話 + cleaned = self._cleanup_idle_sessions() + cleaned_sessions += cleaned + + # 4. 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self._update_cleanup_stats( + CleanupTrigger.AUTO, + cleaned_sessions, + cleanup_duration + ) + + if cleaned_sessions > 0: + debug_log(f"自動清理完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "執行自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"執行自動清理失敗 [錯誤ID: {error_id}]: {e}") + + def trigger_cleanup(self, trigger: CleanupTrigger, force: bool = False) -> int: + """觸發清理操作""" + cleanup_start_time = time.time() + cleaned_sessions = 0 + + try: + debug_log(f"觸發清理操作,觸發器: {trigger.value},強制: {force}") + + if trigger == CleanupTrigger.MEMORY_PRESSURE: + cleaned_sessions = self.web_ui_manager.cleanup_sessions_by_memory_pressure(force) + elif trigger == CleanupTrigger.EXPIRED: + cleaned_sessions = self.web_ui_manager.cleanup_expired_sessions() + elif trigger == CleanupTrigger.CAPACITY: + cleaned_sessions = self._cleanup_by_capacity() + elif trigger == CleanupTrigger.MANUAL: + # 手動清理:組合多種策略 + cleaned_sessions += self.web_ui_manager.cleanup_expired_sessions() + if force: + cleaned_sessions += self.web_ui_manager.cleanup_sessions_by_memory_pressure(force) + else: + # 自動清理 + self._perform_auto_cleanup() + return 0 # 統計已在 _perform_auto_cleanup 中更新 + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self._update_cleanup_stats(trigger, cleaned_sessions, cleanup_duration) + + debug_log(f"清理操作完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒") + return cleaned_sessions + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "觸發清理", "trigger": trigger.value, "force": force}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"觸發清理操作失敗 [錯誤ID: {error_id}]: {e}") + return 0 + + def _cleanup_by_capacity(self) -> int: + """根據容量限制清理會話""" + sessions = self.web_ui_manager.sessions + if len(sessions) <= self.policy.max_sessions: + return 0 + + # 計算需要清理的會話數量 + excess_count = len(sessions) - self.policy.max_sessions + + # 按優先級排序會話(優先清理舊的、非活躍的會話) + session_priorities = [] + for session_id, session in sessions.items(): + # 跳過當前活躍會話(如果啟用保護) + if (self.policy.preserve_active_session and + self.web_ui_manager.current_session and + session.session_id == self.web_ui_manager.current_session.session_id): + continue + + # 計算優先級分數(分數越高越優先清理) + priority_score = 0 + + # 狀態優先級 + if session.status in [SessionStatus.COMPLETED, SessionStatus.ERROR, SessionStatus.TIMEOUT]: + priority_score += 100 + elif session.status == SessionStatus.FEEDBACK_SUBMITTED: + priority_score += 50 + + # 年齡優先級 + age = session.get_age() + priority_score += age / 60 # 每分鐘加1分 + + # 空閒時間優先級 + idle_time = session.get_idle_time() + priority_score += idle_time / 30 # 每30秒加1分 + + session_priorities.append((session_id, session, priority_score)) + + # 按優先級排序並清理 + session_priorities.sort(key=lambda x: x[2], reverse=True) + cleaned_count = 0 + + for i in range(min(excess_count, len(session_priorities))): + session_id, session, _ = session_priorities[i] + try: + session._cleanup_sync_enhanced(CleanupReason.MANUAL) + del self.web_ui_manager.sessions[session_id] + cleaned_count += 1 + except Exception as e: + debug_log(f"容量清理會話 {session_id} 失敗: {e}") + + return cleaned_count + + def _cleanup_expired_sessions(self) -> int: + """清理過期會話""" + expired_sessions = [] + current_time = time.time() + + for session_id, session in self.web_ui_manager.sessions.items(): + # 檢查是否過期 + if session.is_expired(): + expired_sessions.append(session_id) + # 檢查是否超過最大年齡 + elif session.get_age() > self.policy.max_session_age: + expired_sessions.append(session_id) + + # 清理過期會話 + cleaned_count = 0 + for session_id in expired_sessions: + try: + session = self.web_ui_manager.sessions.get(session_id) + if session: + session._cleanup_sync_enhanced(CleanupReason.EXPIRED) + del self.web_ui_manager.sessions[session_id] + cleaned_count += 1 + + # 如果清理的是當前活躍會話,清空當前會話 + if (self.web_ui_manager.current_session and + self.web_ui_manager.current_session.session_id == session_id): + self.web_ui_manager.current_session = None + + except Exception as e: + debug_log(f"清理過期會話 {session_id} 失敗: {e}") + + return cleaned_count + + def _cleanup_idle_sessions(self) -> int: + """清理空閒會話""" + idle_sessions = [] + + for session_id, session in self.web_ui_manager.sessions.items(): + # 跳過當前活躍會話(如果啟用保護) + if (self.policy.preserve_active_session and + self.web_ui_manager.current_session and + session.session_id == self.web_ui_manager.current_session.session_id): + continue + + # 檢查是否空閒時間過長 + if session.get_idle_time() > self.policy.max_idle_time: + idle_sessions.append(session_id) + + # 清理空閒會話 + cleaned_count = 0 + for session_id in idle_sessions: + try: + session = self.web_ui_manager.sessions.get(session_id) + if session: + session._cleanup_sync_enhanced(CleanupReason.EXPIRED) + del self.web_ui_manager.sessions[session_id] + cleaned_count += 1 + + except Exception as e: + debug_log(f"清理空閒會話 {session_id} 失敗: {e}") + + return cleaned_count + + def _update_cleanup_stats(self, trigger: CleanupTrigger, cleaned_count: int, duration: float): + """更新清理統計""" + self.stats.total_cleanups += 1 + self.stats.total_sessions_cleaned += cleaned_count + self.stats.total_cleanup_time += duration + self.stats.last_cleanup_time = datetime.now() + + # 更新平均清理時間 + if self.stats.total_cleanups > 0: + self.stats.average_cleanup_time = self.stats.total_cleanup_time / self.stats.total_cleanups + + # 更新清理效率 + total_sessions = len(self.web_ui_manager.sessions) + cleaned_count + if total_sessions > 0: + self.stats.cleanup_efficiency = cleaned_count / total_sessions + + # 根據觸發器類型更新統計 + if trigger == CleanupTrigger.AUTO: + self.stats.auto_cleanups += 1 + elif trigger == CleanupTrigger.MEMORY_PRESSURE: + self.stats.memory_pressure_cleanups += 1 + elif trigger == CleanupTrigger.EXPIRED: + self.stats.expired_cleanups += 1 + elif trigger == CleanupTrigger.MANUAL: + self.stats.manual_cleanups += 1 + + # 記錄清理歷史 + cleanup_record = { + "timestamp": datetime.now().isoformat(), + "trigger": trigger.value, + "cleaned_count": cleaned_count, + "duration": duration, + "total_sessions_before": total_sessions, + "total_sessions_after": len(self.web_ui_manager.sessions) + } + + self.cleanup_history.append(cleanup_record) + + # 限制歷史記錄數量 + if len(self.cleanup_history) > self.max_history: + self.cleanup_history = self.cleanup_history[-self.max_history:] + + # 調用統計回調 + for callback in self.stats_callbacks: + try: + callback(self.stats, cleanup_record) + except Exception as e: + debug_log(f"統計回調執行失敗: {e}") + + def get_cleanup_statistics(self) -> Dict[str, Any]: + """獲取清理統計數據""" + stats_dict = { + "total_cleanups": self.stats.total_cleanups, + "expired_cleanups": self.stats.expired_cleanups, + "memory_pressure_cleanups": self.stats.memory_pressure_cleanups, + "manual_cleanups": self.stats.manual_cleanups, + "auto_cleanups": self.stats.auto_cleanups, + "total_sessions_cleaned": self.stats.total_sessions_cleaned, + "total_cleanup_time": round(self.stats.total_cleanup_time, 2), + "average_cleanup_time": round(self.stats.average_cleanup_time, 2), + "cleanup_efficiency": round(self.stats.cleanup_efficiency, 3), + "last_cleanup_time": self.stats.last_cleanup_time.isoformat() if self.stats.last_cleanup_time else None, + "is_auto_cleanup_running": self.is_running, + "current_sessions": len(self.web_ui_manager.sessions), + "policy": { + "max_idle_time": self.policy.max_idle_time, + "max_session_age": self.policy.max_session_age, + "max_sessions": self.policy.max_sessions, + "cleanup_interval": self.policy.cleanup_interval, + "enable_auto_cleanup": self.policy.enable_auto_cleanup, + "preserve_active_session": self.policy.preserve_active_session + } + } + + return stats_dict + + def get_cleanup_history(self, limit: int = 20) -> List[Dict[str, Any]]: + """獲取清理歷史記錄""" + return self.cleanup_history[-limit:] if self.cleanup_history else [] + + def add_cleanup_callback(self, callback: Callable): + """添加清理回調函數""" + if callback not in self.cleanup_callbacks: + self.cleanup_callbacks.append(callback) + debug_log("添加清理回調函數") + + def add_stats_callback(self, callback: Callable): + """添加統計回調函數""" + if callback not in self.stats_callbacks: + self.stats_callbacks.append(callback) + debug_log("添加統計回調函數") + + def update_policy(self, **kwargs): + """更新清理策略""" + for key, value in kwargs.items(): + if hasattr(self.policy, key): + setattr(self.policy, key, value) + debug_log(f"更新清理策略 {key} = {value}") + else: + debug_log(f"未知的策略參數: {key}") + + def reset_stats(self): + """重置統計數據""" + self.stats = CleanupStats() + self.cleanup_history.clear() + debug_log("清理統計數據已重置") + + def force_cleanup_all(self, exclude_current: bool = True) -> int: + """強制清理所有會話""" + sessions_to_clean = [] + + for session_id, session in self.web_ui_manager.sessions.items(): + # 是否排除當前活躍會話 + if (exclude_current and + self.web_ui_manager.current_session and + session.session_id == self.web_ui_manager.current_session.session_id): + continue + sessions_to_clean.append(session_id) + + # 清理會話 + cleaned_count = 0 + for session_id in sessions_to_clean: + try: + session = self.web_ui_manager.sessions.get(session_id) + if session: + session._cleanup_sync_enhanced(CleanupReason.MANUAL) + del self.web_ui_manager.sessions[session_id] + cleaned_count += 1 + except Exception as e: + debug_log(f"強制清理會話 {session_id} 失敗: {e}") + + # 更新統計 + self._update_cleanup_stats(CleanupTrigger.MANUAL, cleaned_count, 0.0) + + debug_log(f"強制清理完成,清理了 {cleaned_count} 個會話") + return cleaned_count diff --git a/tests/test_session_cleanup.py b/tests/test_session_cleanup.py new file mode 100644 index 0000000..96900ca --- /dev/null +++ b/tests/test_session_cleanup.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +會話清理優化測試 +================ + +測試 WebFeedbackSession 和 SessionCleanupManager 的清理功能。 +""" + +import asyncio +import pytest +import time +import threading +from unittest.mock import Mock, patch, MagicMock +from datetime import datetime, timedelta + +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.mcp_feedback_enhanced.web.models.feedback_session import ( + WebFeedbackSession, SessionStatus, CleanupReason +) +from src.mcp_feedback_enhanced.web.utils.session_cleanup_manager import ( + SessionCleanupManager, CleanupPolicy, CleanupTrigger +) + + +class TestWebFeedbackSessionCleanup: + """測試 WebFeedbackSession 清理功能""" + + def setup_method(self): + """測試前設置""" + self.session_id = "test_session_001" + self.project_dir = "/tmp/test_project" + self.summary = "測試會話摘要" + + # 創建測試會話 + self.session = WebFeedbackSession( + self.session_id, + self.project_dir, + self.summary, + auto_cleanup_delay=60, # 1分鐘自動清理 + max_idle_time=30 # 30秒最大空閒時間 + ) + + def teardown_method(self): + """測試後清理""" + if hasattr(self, 'session') and self.session: + try: + self.session._cleanup_sync_enhanced(CleanupReason.MANUAL) + except: + pass + + def test_session_initialization(self): + """測試會話初始化""" + assert self.session.session_id == self.session_id + assert self.session.project_directory == self.project_dir + assert self.session.summary == self.summary + assert self.session.status == SessionStatus.WAITING + assert self.session.auto_cleanup_delay == 60 + assert self.session.max_idle_time == 30 + assert self.session.cleanup_timer is not None + assert len(self.session.cleanup_stats) > 0 + + def test_is_expired_by_idle_time(self): + """測試空閒時間過期檢測""" + # 新創建的會話不應該過期 + assert not self.session.is_expired() + + # 模擬空閒時間過長 + self.session.last_activity = time.time() - 40 # 40秒前 + assert self.session.is_expired() + + def test_is_expired_by_status(self): + """測試狀態過期檢測""" + # 設置為錯誤狀態 + self.session.status = SessionStatus.ERROR + self.session.last_activity = time.time() - 400 # 400秒前 + assert self.session.is_expired() + + # 設置為已過期狀態 + self.session.status = SessionStatus.EXPIRED + assert self.session.is_expired() + + def test_get_age_and_idle_time(self): + """測試年齡和空閒時間計算""" + # 測試年齡 + age = self.session.get_age() + assert age >= 0 + assert age < 1 # 剛創建,應該小於1秒 + + # 測試空閒時間 + idle_time = self.session.get_idle_time() + assert idle_time >= 0 + assert idle_time < 1 # 剛創建,應該小於1秒 + + def test_cleanup_timer_scheduling(self): + """測試清理定時器調度""" + # 檢查定時器是否已設置 + assert self.session.cleanup_timer is not None + assert self.session.cleanup_timer.is_alive() + + # 測試延長定時器 + old_timer = self.session.cleanup_timer + self.session.extend_cleanup_timer(120) + + # 應該創建新的定時器 + assert self.session.cleanup_timer != old_timer + assert self.session.cleanup_timer.is_alive() + + def test_cleanup_callbacks(self): + """測試清理回調函數""" + callback_called = False + callback_session = None + callback_reason = None + + def test_callback(session, reason): + nonlocal callback_called, callback_session, callback_reason + callback_called = True + callback_session = session + callback_reason = reason + + # 添加回調 + self.session.add_cleanup_callback(test_callback) + assert len(self.session.cleanup_callbacks) == 1 + + # 執行清理 + self.session._cleanup_sync_enhanced(CleanupReason.MANUAL) + + # 檢查回調是否被調用 + assert callback_called + assert callback_session == self.session + assert callback_reason == CleanupReason.MANUAL + + # 移除回調 + self.session.remove_cleanup_callback(test_callback) + assert len(self.session.cleanup_callbacks) == 0 + + def test_cleanup_stats(self): + """測試清理統計""" + # 初始統計 + stats = self.session.get_cleanup_stats() + assert stats["cleanup_count"] == 0 + assert stats["session_id"] == self.session_id + assert stats["is_active"] == True + + # 執行清理 + self.session._cleanup_sync_enhanced(CleanupReason.EXPIRED) + + # 檢查統計更新 + stats = self.session.get_cleanup_stats() + assert stats["cleanup_count"] == 1 + assert stats["cleanup_reason"] == CleanupReason.EXPIRED.value + assert stats["last_cleanup_time"] is not None + assert stats["cleanup_duration"] >= 0 + + @pytest.mark.asyncio + async def test_async_cleanup(self): + """測試異步清理""" + # 模擬 WebSocket 連接 + mock_websocket = Mock() + mock_websocket.send_json = Mock(return_value=asyncio.Future()) + mock_websocket.send_json.return_value.set_result(None) + mock_websocket.close = Mock(return_value=asyncio.Future()) + mock_websocket.close.return_value.set_result(None) + mock_websocket.client_state.DISCONNECTED = False + + self.session.websocket = mock_websocket + + # 執行異步清理 + await self.session._cleanup_resources_enhanced(CleanupReason.TIMEOUT) + + # 檢查 WebSocket 是否被正確處理 + mock_websocket.send_json.assert_called_once() + + # 檢查清理統計 + stats = self.session.get_cleanup_stats() + assert stats["cleanup_count"] == 1 + assert stats["cleanup_reason"] == CleanupReason.TIMEOUT.value + + def test_status_update_resets_timer(self): + """測試狀態更新重置定時器""" + old_timer = self.session.cleanup_timer + + # 更新狀態為活躍 + self.session.update_status(SessionStatus.ACTIVE, "測試活躍狀態") + + # 檢查定時器是否被重置 + assert self.session.cleanup_timer != old_timer + assert self.session.cleanup_timer.is_alive() + assert self.session.status == SessionStatus.ACTIVE + + +class TestSessionCleanupManager: + """測試 SessionCleanupManager 功能""" + + def setup_method(self): + """測試前設置""" + # 創建模擬的 WebUIManager + self.mock_web_ui_manager = Mock() + self.mock_web_ui_manager.sessions = {} + self.mock_web_ui_manager.current_session = None + self.mock_web_ui_manager.cleanup_expired_sessions = Mock(return_value=0) + self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure = Mock(return_value=0) + + # 創建清理策略 + self.policy = CleanupPolicy( + max_idle_time=30, + max_session_age=300, + max_sessions=5, + cleanup_interval=10, + enable_auto_cleanup=True + ) + + # 創建清理管理器 + self.cleanup_manager = SessionCleanupManager( + self.mock_web_ui_manager, + self.policy + ) + + def teardown_method(self): + """測試後清理""" + if hasattr(self, 'cleanup_manager'): + self.cleanup_manager.stop_auto_cleanup() + + def test_cleanup_manager_initialization(self): + """測試清理管理器初始化""" + assert self.cleanup_manager.web_ui_manager == self.mock_web_ui_manager + assert self.cleanup_manager.policy == self.policy + assert not self.cleanup_manager.is_running + assert self.cleanup_manager.cleanup_thread is None + assert len(self.cleanup_manager.cleanup_callbacks) == 0 + assert len(self.cleanup_manager.cleanup_history) == 0 + + def test_auto_cleanup_start_stop(self): + """測試自動清理啟動和停止""" + # 啟動自動清理 + result = self.cleanup_manager.start_auto_cleanup() + assert result == True + assert self.cleanup_manager.is_running == True + assert self.cleanup_manager.cleanup_thread is not None + assert self.cleanup_manager.cleanup_thread.is_alive() + + # 停止自動清理 + result = self.cleanup_manager.stop_auto_cleanup() + assert result == True + assert self.cleanup_manager.is_running == False + + def test_trigger_cleanup_memory_pressure(self): + """測試內存壓力清理觸發""" + # 設置模擬返回值 + self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.return_value = 3 + + # 觸發內存壓力清理 + cleaned = self.cleanup_manager.trigger_cleanup(CleanupTrigger.MEMORY_PRESSURE, force=True) + + # 檢查結果 + assert cleaned == 3 + self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.assert_called_once_with(True) + + # 檢查統計更新 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] == 1 + assert stats["memory_pressure_cleanups"] == 1 + assert stats["total_sessions_cleaned"] == 3 + + def test_trigger_cleanup_expired(self): + """測試過期清理觸發""" + # 設置模擬返回值 + self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 2 + + # 觸發過期清理 + cleaned = self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED) + + # 檢查結果 + assert cleaned == 2 + self.mock_web_ui_manager.cleanup_expired_sessions.assert_called_once() + + # 檢查統計更新 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] == 1 + assert stats["expired_cleanups"] == 1 + assert stats["total_sessions_cleaned"] == 2 + + def test_cleanup_statistics(self): + """測試清理統計功能""" + # 初始統計 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] == 0 + assert stats["total_sessions_cleaned"] == 0 + assert stats["is_auto_cleanup_running"] == False + + # 執行一些清理操作 + self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1 + self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED) + + self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.return_value = 2 + self.cleanup_manager.trigger_cleanup(CleanupTrigger.MEMORY_PRESSURE) + + # 檢查統計 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] == 2 + assert stats["expired_cleanups"] == 1 + assert stats["memory_pressure_cleanups"] == 1 + assert stats["total_sessions_cleaned"] == 3 + assert stats["average_cleanup_time"] >= 0 + + def test_cleanup_history(self): + """測試清理歷史記錄""" + # 初始歷史為空 + history = self.cleanup_manager.get_cleanup_history() + assert len(history) == 0 + + # 執行清理操作 + self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1 + self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED) + + # 檢查歷史記錄 + history = self.cleanup_manager.get_cleanup_history() + assert len(history) == 1 + + record = history[0] + assert record["trigger"] == CleanupTrigger.EXPIRED.value + assert record["cleaned_count"] == 1 + assert "timestamp" in record + assert "duration" in record + + def test_policy_update(self): + """測試策略更新""" + # 更新策略 + self.cleanup_manager.update_policy( + max_idle_time=60, + max_sessions=10, + enable_auto_cleanup=False + ) + + # 檢查策略是否更新 + assert self.cleanup_manager.policy.max_idle_time == 60 + assert self.cleanup_manager.policy.max_sessions == 10 + assert self.cleanup_manager.policy.enable_auto_cleanup == False + + def test_stats_reset(self): + """測試統計重置""" + # 執行一些操作產生統計 + self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1 + self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED) + + # 檢查有統計數據 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] > 0 + + # 重置統計 + self.cleanup_manager.reset_stats() + + # 檢查統計已重置 + stats = self.cleanup_manager.get_cleanup_statistics() + assert stats["total_cleanups"] == 0 + assert stats["total_sessions_cleaned"] == 0 + + history = self.cleanup_manager.get_cleanup_history() + assert len(history) == 0 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 568f7d986cbbe6e1ad284f04d49adb21d9d92e7f Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 04:22:24 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=E2=9C=A8=20=E6=9B=B4=E6=96=B0=E7=BF=BB?= =?UTF-8?q?=E8=AD=AF=E6=96=87=E4=BB=B6=EF=BC=8C=E5=B0=87=E3=80=8C=E5=90=88?= =?UTF-8?q?=E4=BD=B5=E6=A8=A1=E5=BC=8F=E3=80=8D=E6=9B=B4=E5=90=8D=E7=82=BA?= =?UTF-8?q?=E3=80=8C=E5=B7=A5=E4=BD=9C=E5=8D=80=E3=80=8D=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E7=9B=B8=E9=97=9C=E6=8F=8F=E8=BF=B0=EF=BC=9B?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=87=AA=E5=8B=95=E5=88=B7=E6=96=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E7=95=8C=E9=9D=A2=E5=85=83=E7=B4=A0=E5=8F=8A?= =?UTF-8?q?=E6=A8=A3=E5=BC=8F=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=80=85=E9=AB=94=E9=A9=97=E8=88=87=E7=95=8C=E9=9D=A2=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/locales/en/translation.json | 23 +- .../web/locales/zh-CN/translation.json | 23 +- .../web/locales/zh-TW/translation.json | 23 +- .../web/routes/main_routes.py | 10 +- .../web/static/css/styles.css | 124 ++++++++ .../web/static/js/app.js | 293 ++++++++++++++---- .../web/static/js/i18n.js | 4 + .../web/templates/feedback.html | 59 ++-- 8 files changed, 448 insertions(+), 111 deletions(-) diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index f82e688..a77db81 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ Commands", "command": "⚡ Commands", "settings": "⚙️ Settings", - "combined": "📝 Combined Mode", + "combined": "📝 Workspace", "about": "ℹ️ About" }, "feedback": { @@ -73,7 +73,7 @@ "history": "Command History" }, "combined": { - "description": "Combined mode: AI summary and feedback input are on the same page for easy comparison.", + "description": "AI summary and feedback input are on the same page for easy comparison.", "summaryTitle": "📋 AI Work Summary", "feedbackTitle": "💬 Provide Feedback" }, @@ -86,12 +86,10 @@ "interface": "Interface Settings", "layoutMode": "Interface Layout Mode", "layoutModeDesc": "Select how AI summary and feedback input are displayed", - "separateMode": "Separate Mode", - "separateModeDesc": "AI summary and feedback are in separate tabs", - "combinedVertical": "Combined Mode (Vertical Layout)", - "combinedVerticalDesc": "AI summary on top, feedback input below, both on the same page", - "combinedHorizontal": "Combined Mode (Horizontal Layout)", - "combinedHorizontalDesc": "AI summary on left, feedback input on right, expanding summary viewing area", + "combinedVertical": "Vertical Layout", + "combinedVerticalDesc": "AI summary on top, feedback input below, suitable for standard screens", + "combinedHorizontal": "Horizontal Layout", + "combinedHorizontalDesc": "AI summary on left, feedback input on right, suitable for widescreen displays", "autoClose": "Auto Close Page", "autoCloseDesc": "Automatically close page after submitting feedback", "theme": "Theme", @@ -172,6 +170,15 @@ "timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.", "closing": "Closing..." }, + "autoRefresh": { + "enable": "Auto Detect", + "seconds": "seconds", + "disabled": "Disabled", + "enabled": "Detecting", + "checking": "Checking", + "detected": "Detected", + "error": "Failed" + }, "dynamic": { "aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!", diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index ca91af0..e1944df 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ 命令", "command": "⚡ 命令", "settings": "⚙️ 设置", - "combined": "📝 合并模式", + "combined": "📝 工作区", "about": "ℹ️ 关于" }, "feedback": { @@ -73,7 +73,7 @@ "history": "命令历史" }, "combined": { - "description": "合并模式:AI 摘要和反馈输入在同一页面中,方便对照查看。", + "description": "AI 摘要和反馈输入在同一页面中,方便对照查看。", "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供反馈" }, @@ -86,12 +86,10 @@ "interface": "界面设定", "layoutMode": "界面布局模式", "layoutModeDesc": "选择 AI 摘要和反馈输入的显示方式", - "separateMode": "分离模式", - "separateModeDesc": "AI 摘要和反馈分别在不同页签", - "combinedVertical": "合并模式(垂直布局)", - "combinedVerticalDesc": "AI 摘要在上,反馈输入在下,摘要和反馈在同一页面", - "combinedHorizontal": "合并模式(水平布局)", - "combinedHorizontalDesc": "AI 摘要在左,反馈输入在右,增大摘要可视区域", + "combinedVertical": "垂直布局", + "combinedVerticalDesc": "AI 摘要在上,反馈输入在下,适合标准屏幕使用", + "combinedHorizontal": "水平布局", + "combinedHorizontalDesc": "AI 摘要在左,反馈输入在右,适合宽屏幕使用", "autoClose": "自动关闭页面", "autoCloseDesc": "提交回馈后自动关闭页面", "theme": "主题", @@ -172,6 +170,15 @@ "timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。", "closing": "正在关闭..." }, + "autoRefresh": { + "enable": "自动检测", + "seconds": "秒", + "disabled": "停用", + "enabled": "检测中", + "checking": "检查中", + "detected": "已检测", + "error": "失败" + }, "dynamic": { "aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈!", diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index c43af12..4cdd204 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ 命令", "command": "⚡ 命令", "settings": "⚙️ 設定", - "combined": "📝 合併模式", + "combined": "📝 工作區", "about": "ℹ️ 關於" }, "feedback": { @@ -73,7 +73,7 @@ "history": "命令歷史" }, "combined": { - "description": "合併模式:AI 摘要和回饋輸入在同一頁面中,方便對照查看。", + "description": "AI 摘要和回饋輸入在同一頁面中,方便對照查看。", "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供回饋" }, @@ -86,12 +86,10 @@ "interface": "介面設定", "layoutMode": "界面佈局模式", "layoutModeDesc": "選擇 AI 摘要和回饋輸入的顯示方式", - "separateMode": "分離模式", - "separateModeDesc": "AI 摘要和回饋分別在不同頁籤", - "combinedVertical": "合併模式(垂直布局)", - "combinedVerticalDesc": "AI 摘要在上,回饋輸入在下,摘要和回饋在同一頁面", - "combinedHorizontal": "合併模式(水平布局)", - "combinedHorizontalDesc": "AI 摘要在左,回饋輸入在右,增大摘要可視區域", + "combinedVertical": "垂直佈局", + "combinedVerticalDesc": "AI 摘要在上,回饋輸入在下,適合標準螢幕使用", + "combinedHorizontal": "水平佈局", + "combinedHorizontalDesc": "AI 摘要在左,回饋輸入在右,適合寬螢幕使用", "autoClose": "自動關閉頁面", "autoCloseDesc": "提交回饋後自動關閉頁面", "theme": "主題", @@ -172,6 +170,15 @@ "timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。", "closing": "正在關閉..." }, + "autoRefresh": { + "enable": "自動檢測", + "seconds": "秒", + "disabled": "停用", + "enabled": "檢測中", + "checking": "檢查中", + "detected": "已檢測", + "error": "失敗" + }, "dynamic": { "aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋!", diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index a8ca0eb..f7f3097 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -33,15 +33,15 @@ def load_user_layout_settings() -> str: if settings_file.exists(): with open(settings_file, 'r', encoding='utf-8') as f: settings = json.load(f) - layout_mode = settings.get('layoutMode', 'separate') + layout_mode = settings.get('layoutMode', 'combined-vertical') debug_log(f"從設定檔案載入佈局模式: {layout_mode}") return layout_mode else: - debug_log("設定檔案不存在,使用預設佈局模式: separate") - return 'separate' + debug_log("設定檔案不存在,使用預設佈局模式: combined-vertical") + return 'combined-vertical' except Exception as e: - debug_log(f"載入佈局設定失敗: {e},使用預設佈局模式: separate") - return 'separate' + debug_log(f"載入佈局設定失敗: {e},使用預設佈局模式: combined-vertical") + return 'combined-vertical' def setup_routes(manager: 'WebUIManager'): diff --git a/src/mcp_feedback_enhanced/web/static/css/styles.css b/src/mcp_feedback_enhanced/web/static/css/styles.css index b268490..518fe87 100644 --- a/src/mcp_feedback_enhanced/web/static/css/styles.css +++ b/src/mcp_feedback_enhanced/web/static/css/styles.css @@ -1265,4 +1265,128 @@ body { .compatibility-hint-btn:hover { background: #f57c00; +} + +/* 自動刷新控制項樣式 */ +.section-header-with-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +/* AI 工作摘要標題樣式 */ +h3.combined-section-title { + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.auto-refresh-controls { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 6px 10px; + transition: all 0.3s ease; +} + +.auto-refresh-controls:hover { + border-color: var(--accent-color); + background: var(--surface-color); +} + +.auto-refresh-toggle { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + user-select: none; +} + +.auto-refresh-toggle input[type="checkbox"] { + appearance: none; + width: 14px; + height: 14px; + border: 1px solid var(--border-color); + border-radius: 2px; + background: var(--bg-primary); + cursor: pointer; + transition: all 0.3s ease; + position: relative; +} + +.auto-refresh-toggle input[type="checkbox"]:checked { + background: var(--accent-color); + border-color: var(--accent-color); +} + +.auto-refresh-toggle input[type="checkbox"]:checked::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 9px; + font-weight: bold; +} + +.toggle-label { + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} + +.auto-refresh-interval { + display: flex; + align-items: center; + gap: 2px; +} + +.auto-refresh-interval input[type="number"] { + width: 50px; + background: var(--bg-primary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + padding: 4px 6px; + font-size: 13px; + text-align: center; +} + +.auto-refresh-interval input[type="number"]:focus { + outline: none; + border-color: var(--accent-color); +} + +.interval-unit { + color: var(--text-secondary); + font-size: 14px; + white-space: nowrap; +} + +.auto-refresh-status { + display: flex; + align-items: center; + gap: 3px; + padding: 1px 4px; + background: var(--bg-primary); + border-radius: 3px; + border: 1px solid var(--border-color); +} + +.auto-refresh-status .status-indicator { + font-size: 13px; +} + +.auto-refresh-status .status-text { + color: var(--text-secondary); + font-size: 14px; + white-space: nowrap; } \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 0b512a6..fa46a44 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -185,11 +185,17 @@ class FeedbackApp { // 設定 this.autoClose = false; - this.layoutMode = 'separate'; + this.layoutMode = 'combined-vertical'; // 語言設定 this.currentLanguage = 'zh-TW'; + // 自動刷新設定 + this.autoRefreshEnabled = false; + this.autoRefreshInterval = 5; // 默認5秒 + this.autoRefreshTimer = null; + this.lastKnownSessionId = null; + this.init(); } @@ -223,6 +229,9 @@ class FeedbackApp { // 確保狀態指示器使用正確的翻譯(在國際化系統載入後) this.updateStatusIndicators(); + // 初始化自動刷新功能 + this.initAutoRefresh(); + // 設置頁面關閉時的清理 window.addEventListener('beforeunload', () => { if (this.tabManager) { @@ -231,6 +240,9 @@ class FeedbackApp { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + } }); console.log('MCP Feedback Enhanced 應用程式初始化完成'); @@ -259,6 +271,12 @@ class FeedbackApp { this.commandOutput = document.getElementById('commandOutput'); this.runCommandBtn = document.getElementById('runCommandBtn'); + // 自動刷新相關元素 + this.autoRefreshCheckbox = document.getElementById('autoRefreshEnabled'); + this.autoRefreshIntervalInput = document.getElementById('autoRefreshInterval'); + this.refreshStatusIndicator = document.getElementById('refreshStatusIndicator'); + this.refreshStatusText = document.getElementById('refreshStatusText'); + // 動態初始化圖片相關元素 this.initImageElements(); } @@ -1016,7 +1034,7 @@ class FeedbackApp { if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = Math.min(3000 * this.reconnectAttempts, 15000); // 最大延遲15秒 - console.log(`${delay/1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); + console.log(`${delay / 1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); setTimeout(() => { console.log(`🔄 開始重連 WebSocket... (第${this.reconnectAttempts}次)`); this.setupWebSocket(); @@ -1410,9 +1428,9 @@ class FeedbackApp { word-wrap: break-word; `; messageDiv.textContent = message; - + document.body.appendChild(messageDiv); - + // 3秒後自動移除 setTimeout(() => { if (messageDiv.parentNode) { @@ -1450,12 +1468,12 @@ class FeedbackApp { async loadFeedbackInterface(sessionInfo) { if (!this.mainContainer) return; - + this.sessionInfo = sessionInfo; - + // 載入完整的回饋界面 this.mainContainer.innerHTML = await this.generateFeedbackHTML(sessionInfo); - + // 重新設置事件監聽器 this.setupFeedbackEventListeners(); } @@ -1801,6 +1819,8 @@ class FeedbackApp { this.currentLanguage = settings.language || 'zh-TW'; this.imageSizeLimit = settings.imageSizeLimit || 0; this.enableBase64Detail = settings.enableBase64Detail || false; + this.autoRefreshEnabled = settings.autoRefreshEnabled || false; + this.autoRefreshInterval = settings.autoRefreshInterval || 5; // 處理 activeTab 設定 if (settings.activeTab) { @@ -1846,6 +1866,8 @@ class FeedbackApp { language: this.currentLanguage, imageSizeLimit: this.imageSizeLimit, enableBase64Detail: this.enableBase64Detail, + autoRefreshEnabled: this.autoRefreshEnabled, + autoRefreshInterval: this.autoRefreshInterval, activeTab: this.currentTab }; @@ -1904,6 +1926,15 @@ class FeedbackApp { if (this.enableBase64DetailCheckbox) { this.enableBase64DetailCheckbox.checked = this.enableBase64Detail; } + + // 應用自動刷新設定 + if (this.autoRefreshCheckbox) { + this.autoRefreshCheckbox.checked = this.autoRefreshEnabled; + } + + if (this.autoRefreshIntervalInput) { + this.autoRefreshIntervalInput.value = this.autoRefreshInterval; + } } applyLayoutMode() { @@ -1927,18 +1958,11 @@ class FeedbackApp { // 同步合併佈局和分頁中的內容 this.syncCombinedLayoutContent(); - // 如果是合併模式,確保內容同步 - if (this.layoutMode.startsWith('combined')) { - this.setupCombinedModeSync(); - // 如果當前頁籤不是合併模式,則切換到合併模式頁籤 - if (this.currentTab !== 'combined') { - this.currentTab = 'combined'; - } - } else { - // 分離模式時,如果當前頁籤是合併模式,則切換到回饋頁籤 - if (this.currentTab === 'combined') { - this.currentTab = 'feedback'; - } + // 確保合併模式內容同步 + this.setupCombinedModeSync(); + // 如果當前頁籤不是合併模式,則切換到合併模式頁籤 + if (this.currentTab !== 'combined') { + this.currentTab = 'combined'; } } @@ -1947,17 +1971,10 @@ class FeedbackApp { const feedbackTab = document.querySelector('.tab-button[data-tab="feedback"]'); const summaryTab = document.querySelector('.tab-button[data-tab="summary"]'); - if (this.layoutMode.startsWith('combined')) { - // 合併模式:顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 - if (combinedTab) combinedTab.style.display = 'inline-block'; - if (feedbackTab) feedbackTab.style.display = 'none'; - if (summaryTab) summaryTab.style.display = 'none'; - } else { - // 分離模式:隱藏合併模式頁籤,顯示回饋和AI摘要頁籤 - if (combinedTab) combinedTab.style.display = 'none'; - if (feedbackTab) feedbackTab.style.display = 'inline-block'; - if (summaryTab) summaryTab.style.display = 'inline-block'; - } + // 只使用合併模式:顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 + if (combinedTab) combinedTab.style.display = 'inline-block'; + if (feedbackTab) feedbackTab.style.display = 'none'; + if (summaryTab) summaryTab.style.display = 'none'; } syncCombinedLayoutContent() { @@ -2012,27 +2029,6 @@ class FeedbackApp { } setupCombinedModeSync() { - // 設置文字輸入的雙向同步 - const feedbackText = document.getElementById('feedbackText'); - const combinedFeedbackText = document.getElementById('combinedFeedbackText'); - - if (feedbackText && combinedFeedbackText) { - // 移除舊的事件監聽器(如果存在) - feedbackText.removeEventListener('input', this.syncToCombinetText); - combinedFeedbackText.removeEventListener('input', this.syncToSeparateText); - - // 添加新的事件監聽器 - this.syncToCombinetText = (e) => { - combinedFeedbackText.value = e.target.value; - }; - this.syncToSeparateText = (e) => { - feedbackText.value = e.target.value; - }; - - feedbackText.addEventListener('input', this.syncToCombinetText); - combinedFeedbackText.addEventListener('input', this.syncToSeparateText); - } - // 設置圖片設定的同步 this.setupImageSettingsSync(); @@ -2111,11 +2107,13 @@ class FeedbackApp { resetSettings() { localStorage.removeItem('mcp-feedback-settings'); - this.layoutMode = 'separate'; + this.layoutMode = 'combined-vertical'; this.autoClose = false; this.currentLanguage = 'zh-TW'; this.imageSizeLimit = 0; this.enableBase64Detail = false; + this.autoRefreshEnabled = false; + this.autoRefreshInterval = 5; this.applySettings(); this.saveSettings(); } @@ -2169,6 +2167,197 @@ class FeedbackApp { // 不需要手動複製,updateStatusIndicator() 會處理所有狀態指示器 } + /** + * 初始化自動刷新功能 + */ + initAutoRefresh() { + console.log('🔄 初始化自動刷新功能...'); + + // 檢查必要元素是否存在 + if (!this.autoRefreshCheckbox || !this.autoRefreshIntervalInput) { + console.warn('⚠️ 自動刷新元素不存在,跳過初始化'); + return; + } + + // 設置開關事件監聽器 + this.autoRefreshCheckbox.addEventListener('change', (e) => { + this.autoRefreshEnabled = e.target.checked; + this.handleAutoRefreshToggle(); + this.saveSettings(); + }); + + // 設置間隔輸入事件監聽器 + this.autoRefreshIntervalInput.addEventListener('change', (e) => { + const newInterval = parseInt(e.target.value); + if (newInterval >= 5 && newInterval <= 300) { + this.autoRefreshInterval = newInterval; + this.saveSettings(); + + // 如果自動刷新已啟用,重新啟動定時器 + if (this.autoRefreshEnabled) { + this.stopAutoRefresh(); + this.startAutoRefresh(); + } + } + }); + + // 從設定中恢復狀態 + this.autoRefreshCheckbox.checked = this.autoRefreshEnabled; + this.autoRefreshIntervalInput.value = this.autoRefreshInterval; + + // 延遲更新狀態指示器,確保 i18n 已完全載入 + setTimeout(() => { + this.updateAutoRefreshStatus(); + }, 100); + + console.log('✅ 自動刷新功能初始化完成'); + } + + /** + * 處理自動刷新開關切換 + */ + handleAutoRefreshToggle() { + if (this.autoRefreshEnabled) { + this.startAutoRefresh(); + } else { + this.stopAutoRefresh(); + } + this.updateAutoRefreshStatus(); + } + + /** + * 啟動自動刷新 + */ + startAutoRefresh() { + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + } + + // 記錄當前會話 ID + this.lastKnownSessionId = this.currentSessionId; + + this.autoRefreshTimer = setInterval(() => { + this.checkForSessionUpdate(); + }, this.autoRefreshInterval * 1000); + + console.log(`🔄 自動刷新已啟動,間隔: ${this.autoRefreshInterval}秒`); + } + + /** + * 停止自動刷新 + */ + stopAutoRefresh() { + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + this.autoRefreshTimer = null; + } + console.log('⏸️ 自動刷新已停止'); + } + + /** + * 檢查會話更新 + */ + async checkForSessionUpdate() { + try { + this.updateAutoRefreshStatus('checking'); + + const response = await fetch('/api/current-session'); + if (!response.ok) { + throw new Error(`API 請求失敗: ${response.status}`); + } + + const sessionData = await response.json(); + + // 檢查會話 ID 是否變化 + if (sessionData.session_id && sessionData.session_id !== this.lastKnownSessionId) { + console.log(`🔄 檢測到新會話: ${this.lastKnownSessionId} -> ${sessionData.session_id}`); + + // 更新記錄的會話 ID + this.lastKnownSessionId = sessionData.session_id; + this.currentSessionId = sessionData.session_id; + + // 觸發局部刷新 + await this.updatePageContentPartially(); + + this.updateAutoRefreshStatus('detected'); + + // 短暫顯示檢測成功狀態,然後恢復為檢測中 + setTimeout(() => { + if (this.autoRefreshEnabled) { + this.updateAutoRefreshStatus('enabled'); + } + }, 2000); + } else { + this.updateAutoRefreshStatus('enabled'); + } + + } catch (error) { + console.error('❌ 自動刷新檢測失敗:', error); + this.updateAutoRefreshStatus('error'); + + // 短暫顯示錯誤狀態,然後恢復 + setTimeout(() => { + if (this.autoRefreshEnabled) { + this.updateAutoRefreshStatus('enabled'); + } + }, 3000); + } + } + + /** + * 更新自動刷新狀態指示器 + */ + updateAutoRefreshStatus(status = null) { + console.log(`🔧 updateAutoRefreshStatus 被調用,status: ${status}`); + console.log(`🔧 refreshStatusIndicator: ${this.refreshStatusIndicator ? 'found' : 'null'}`); + console.log(`🔧 refreshStatusText: ${this.refreshStatusText ? 'found' : 'null'}`); + + if (!this.refreshStatusIndicator || !this.refreshStatusText) { + console.log(`⚠️ 自動檢測狀態元素未找到,跳過更新`); + return; + } + + let indicator = '⏸️'; + let textKey = 'autoRefresh.disabled'; + + if (status === null) { + status = this.autoRefreshEnabled ? 'enabled' : 'disabled'; + } + + switch (status) { + case 'enabled': + indicator = '🔄'; + textKey = 'autoRefresh.enabled'; + break; + case 'checking': + indicator = '🔍'; + textKey = 'autoRefresh.checking'; + break; + case 'detected': + indicator = '✅'; + textKey = 'autoRefresh.detected'; + break; + case 'error': + indicator = '❌'; + textKey = 'autoRefresh.error'; + break; + case 'disabled': + default: + indicator = '⏸️'; + textKey = 'autoRefresh.disabled'; + break; + } + + this.refreshStatusIndicator.textContent = indicator; + + // 使用多語系翻譯 + + const translatedText = window.i18nManager.t(textKey); + console.log(`🔄 自動檢測狀態翻譯: ${textKey} -> ${translatedText} (語言: ${window.i18nManager.currentLanguage})`); + this.refreshStatusText.textContent = translatedText; + + } + } diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index 9bd4d42..93bcb38 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -178,6 +178,10 @@ class I18nManager { if (window.feedbackApp) { window.feedbackApp.updateUIState(); window.feedbackApp.updateStatusIndicator(); + // 更新自動檢測狀態文字 + if (window.feedbackApp.updateAutoRefreshStatus) { + window.feedbackApp.updateAutoRefreshStatus(); + } } } diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 2318419..4ae16be 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -23,18 +23,8 @@ /* 頁面特定的佈局模式樣式 */ - /* 佈局模式樣式 */ - /* 預設分離模式 - 顯示回饋和AI摘要頁籤,隱藏合併模式頁籤 */ - body.layout-separate .tab-button[data-tab="combined"] { - display: none; - } - - body.layout-separate .tab-button[data-tab="feedback"], - body.layout-separate .tab-button[data-tab="summary"] { - display: inline-block; - } - - /* 合併模式 - 顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 */ + /* 佈局模式樣式 - 工作區模式 */ + /* 工作區模式 - 顯示工作區頁籤,隱藏回饋和AI摘要頁籤 */ body.layout-combined-vertical .tab-button[data-tab="combined"], body.layout-combined-horizontal .tab-button[data-tab="combined"] { display: inline-block; @@ -62,7 +52,7 @@ - /* 合併模式分頁的水平佈局樣式 */ + /* 工作區分頁的水平佈局樣式 */ #tab-combined.active.combined-horizontal .combined-content { display: flex !important; flex-direction: row !important; @@ -99,7 +89,7 @@ min-height: 200px; } - /* 合併模式分頁的垂直佈局樣式 */ + /* 工作區分頁的垂直佈局樣式 */ #tab-combined.active.combined-vertical .combined-content { display: flex !important; flex-direction: column !important; @@ -144,7 +134,7 @@ flex: 1; } - /* 合併模式基礎樣式 */ + /* 工作區基礎樣式 */ .combined-section { background: var(--bg-tertiary); border: 1px solid var(--border-color); @@ -395,9 +385,9 @@
- +
- +
- 合併模式:AI 摘要和回饋輸入在同一頁面中,方便對照查看。 + AI 摘要和回饋輸入在同一頁面中,方便對照查看。
-

📋 AI 工作摘要

+
+

📋 AI 工作摘要

+
+ +
+ + +
+
+ ⏸️ + +
+
+
{{ summary }}
@@ -570,23 +576,16 @@
- - -
-
- +
From fa532a647b7ff5fc1654da810ed11ad6dbf4e318 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 04:34:54 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=E2=9C=A8=20=E5=84=AA=E5=8C=96=E5=9C=96?= =?UTF-8?q?=E7=89=87=E4=BA=8B=E4=BB=B6=E7=9B=A3=E8=81=BD=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E8=A8=AD=E7=BD=AE=E8=88=87=E7=A7=BB=E9=99=A4=E9=82=8F=E8=BC=AF?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=97=A5=E8=AA=8C=E8=BC=B8=E5=87=BA?= =?UTF-8?q?=E4=BB=A5=E4=BE=BF=E6=96=BC=E8=AA=BF=E8=A9=A6=EF=BC=8C=E7=A2=BA?= =?UTF-8?q?=E4=BF=9D=E4=BA=8B=E4=BB=B6=E8=99=95=E7=90=86=E7=9A=84=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E6=80=A7=E8=88=87=E6=AD=A3=E7=A2=BA=E6=80=A7=E3=80=82?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E9=87=8D=E8=A4=87=E7=9A=84=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E7=9B=A3=E8=81=BD=E5=99=A8=E8=A8=AD=E7=BD=AE=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E7=B5=B1=E4=B8=80=E7=AE=A1=E7=90=86=E5=9C=96=E7=89=87=E4=B8=8A?= =?UTF-8?q?=E5=82=B3=E5=8A=9F=E8=83=BD=E7=9A=84=E4=BA=8B=E4=BB=B6=E7=9B=A3?= =?UTF-8?q?=E8=81=BD=E5=99=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/static/js/app.js | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index fa46a44..07c356d 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -427,15 +427,28 @@ class FeedbackApp { * 設置圖片事件監聽器 */ setupImageEventListeners() { + console.log(`🖼️ 設置圖片事件監聽器 - imageInput: ${this.imageInput?.id}, imageUploadArea: ${this.imageUploadArea?.id}`); + // 文件選擇事件 this.imageChangeHandler = (e) => { + console.log(`📁 文件選擇事件觸發 - input: ${e.target.id}, files: ${e.target.files.length}`); this.handleFileSelect(e.target.files); }; this.imageInput.addEventListener('change', this.imageChangeHandler); - // 點擊上傳區域 - this.imageClickHandler = () => { - this.imageInput.click(); + // 點擊上傳區域 - 使用更安全的方式確保只觸發對應的 input + this.imageClickHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + + // 確保我們觸發的是正確的 input 元素 + const targetInput = this.imageInput; + if (targetInput) { + console.log(`🖱️ 點擊上傳區域 - 觸發 input: ${targetInput.id}`); + targetInput.click(); + } else { + console.warn('⚠️ 沒有找到對應的 input 元素'); + } }; this.imageUploadArea.addEventListener('click', this.imageClickHandler); @@ -469,8 +482,10 @@ class FeedbackApp { // 重新初始化圖片元素(確保使用最新的佈局模式) this.initImageElements(); + console.log(`🔍 檢查圖片元素 - imageUploadArea: ${this.imageUploadArea?.id || 'null'}, imageInput: ${this.imageInput?.id || 'null'}`); + if (!this.imageUploadArea || !this.imageInput) { - console.warn('⚠️ 圖片處理初始化失敗 - 缺少必要元素'); + console.warn(`⚠️ 圖片處理初始化失敗 - imageUploadArea: ${!!this.imageUploadArea}, imageInput: ${!!this.imageInput}`); return; } @@ -504,6 +519,7 @@ class FeedbackApp { * 移除舊的圖片事件監聽器 */ removeImageEventListeners() { + // 移除當前主要元素的事件監聽器 if (this.imageInput && this.imageChangeHandler) { this.imageInput.removeEventListener('change', this.imageChangeHandler); } @@ -521,6 +537,32 @@ class FeedbackApp { this.imageUploadArea.removeEventListener('drop', this.imageDropHandler); } } + + // 額外清理:移除所有可能的圖片上傳區域的 click 事件監聽器 + const allImageUploadAreas = [ + document.getElementById('feedbackImageUploadArea'), + document.getElementById('combinedImageUploadArea') + ].filter(area => area); + + allImageUploadAreas.forEach(area => { + if (area && this.imageClickHandler) { + area.removeEventListener('click', this.imageClickHandler); + console.log(`🧹 已移除 ${area.id} 的 click 事件監聽器`); + } + }); + + // 清理所有可能的 input 元素的 change 事件監聽器 + const allImageInputs = [ + document.getElementById('feedbackImageInput'), + document.getElementById('combinedImageInput') + ].filter(input => input); + + allImageInputs.forEach(input => { + if (input && this.imageChangeHandler) { + input.removeEventListener('change', this.imageChangeHandler); + console.log(`🧹 已移除 ${input.id} 的 change 事件監聽器`); + } + }); } /** @@ -2073,36 +2115,9 @@ class FeedbackApp { setupImageUploadSync() { // 設置合併模式的圖片上傳功能 - const combinedImageInput = document.getElementById('combinedImageInput'); - const combinedImageUploadArea = document.getElementById('combinedImageUploadArea'); - - if (combinedImageInput && combinedImageUploadArea) { - // 簡化的圖片上傳同步 - 只需要基本的事件監聽器 - combinedImageInput.addEventListener('change', (e) => { - this.handleFileSelect(e.target.files); - }); - - combinedImageUploadArea.addEventListener('click', () => { - combinedImageInput.click(); - }); - - // 拖放事件 - combinedImageUploadArea.addEventListener('dragover', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.add('dragover'); - }); - - combinedImageUploadArea.addEventListener('dragleave', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.remove('dragover'); - }); - - combinedImageUploadArea.addEventListener('drop', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.remove('dragover'); - this.handleFileSelect(e.dataTransfer.files); - }); - } + // 注意:所有事件監聽器現在由 setupImageEventListeners() 統一處理 + // 這個函數保留用於未來可能的同步邏輯,但不再設置重複的事件監聽器 + console.log('🔄 setupImageUploadSync: 事件監聽器由 setupImageEventListeners() 統一處理'); } resetSettings() { From e021d8bd2a445dd8b66898722647d0b37a835d69 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 04:41:23 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E2=9C=A8=20=E5=9C=A8=E4=B8=BB=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E4=B8=AD=E6=96=B0=E5=A2=9E=E6=9C=83=E8=A9=B1ID?= =?UTF-8?q?=E8=87=B3JSON=E9=9F=BF=E6=87=89=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E6=9C=83=E8=A9=B1=E7=AE=A1=E7=90=86=E7=9A=84=E5=8F=AF=E8=BF=BD?= =?UTF-8?q?=E8=B9=A4=E6=80=A7=E8=88=87=E8=AA=BF=E8=A9=A6=E4=BE=BF=E5=88=A9?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/web/routes/main_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index f7f3097..7830534 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -140,6 +140,7 @@ def setup_routes(manager: 'WebUIManager'): ) return JSONResponse(content={ + "session_id": current_session.session_id, "project_directory": current_session.project_directory, "summary": current_session.summary, "feedback_completed": current_session.feedback_completed.is_set(), From 1607d9802760a64f247b589fd481ceeea0136c94 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 04:45:51 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E2=9C=A8=20=E5=9C=A8=20FeedbackApp=20?= =?UTF-8?q?=E4=B8=AD=E6=96=B0=E5=A2=9E=E6=97=A5=E8=AA=8C=E8=BC=B8=E5=87=BA?= =?UTF-8?q?=EF=BC=8C=E9=A1=AF=E7=A4=BA=E8=87=AA=E5=8B=95=E6=AA=A2=E6=B8=AC?= =?UTF-8?q?=E7=8D=B2=E5=8F=96=E7=9A=84=E6=9C=83=E8=A9=B1=E6=95=B8=E6=93=9A?= =?UTF-8?q?=E5=8F=8A=E6=9C=83=E8=A9=B1=20ID=20=E8=AE=8A=E5=8C=96=E6=83=85?= =?UTF-8?q?=E6=B3=81=EF=BC=8C=E6=8F=90=E5=8D=87=E8=AA=BF=E8=A9=A6=E4=BE=BF?= =?UTF-8?q?=E5=88=A9=E6=80=A7=E8=88=87=E5=8F=AF=E8=BF=BD=E8=B9=A4=E6=80=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/web/static/js/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 07c356d..2942442 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -2282,6 +2282,9 @@ class FeedbackApp { } const sessionData = await response.json(); + console.log(`🔍 自動檢測獲取到會話數據:`, sessionData); + console.log(`🔍 當前記錄的會話 ID: ${this.lastKnownSessionId}`); + console.log(`🔍 API 返回的會話 ID: ${sessionData.session_id}`); // 檢查會話 ID 是否變化 if (sessionData.session_id && sessionData.session_id !== this.lastKnownSessionId) { @@ -2303,6 +2306,7 @@ class FeedbackApp { } }, 2000); } else { + console.log(`🔍 會話 ID 未變化,跳過更新`); this.updateAutoRefreshStatus('enabled'); } From da8128c5bbbc1d53ae331f7f17e565e17ffd3688 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 7 Jun 2025 04:54:28 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E2=9C=A8=20=E6=9B=B4=E6=96=B0=20Feedback?= =?UTF-8?q?App=20=E7=9A=84=E5=9B=9E=E9=A5=8B=E7=8B=80=E6=85=8B=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=9C=83=E8=A9=B1=20ID=20?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E9=82=8F=E8=BC=AF=EF=BC=8C=E5=84=AA=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E6=AA=A2=E6=B8=AC=E5=8A=9F=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E8=AA=8C=E8=BC=B8=E5=87=BA=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E8=AA=BF=E8=A9=A6=E4=BE=BF=E5=88=A9=E6=80=A7=E8=88=87=E5=8F=AF?= =?UTF-8?q?=E8=BF=BD=E8=B9=A4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/static/js/app.js | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 2942442..762f427 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -1260,16 +1260,22 @@ class FeedbackApp { const sessionData = await response.json(); console.log('📥 獲取到最新會話資料:', sessionData); - // 2. 更新 AI 摘要內容 + // 2. 重置回饋狀態為等待新回饋(使用新的會話 ID) + if (sessionData.session_id) { + this.setFeedbackState('waiting_for_feedback', sessionData.session_id); + console.log('🔄 已重置回饋狀態為等待新回饋'); + } + + // 3. 更新 AI 摘要內容 this.updateAISummaryContent(sessionData.summary); - // 3. 重置回饋表單 + // 4. 重置回饋表單 this.resetFeedbackForm(); - // 4. 更新狀態指示器 + // 5. 更新狀態指示器 this.updateStatusIndicators(); - // 5. 更新頁面標題 + // 6. 更新頁面標題 if (sessionData.project_directory) { const projectName = sessionData.project_directory.split(/[/\\]/).pop(); document.title = `MCP Feedback - ${projectName}`; @@ -2223,6 +2229,12 @@ class FeedbackApp { // 延遲更新狀態指示器,確保 i18n 已完全載入 setTimeout(() => { this.updateAutoRefreshStatus(); + + // 如果自動刷新已啟用,啟動自動檢測 + if (this.autoRefreshEnabled) { + console.log('🔄 自動刷新已啟用,啟動自動檢測...'); + this.startAutoRefresh(); + } }, 100); console.log('✅ 自動刷新功能初始化完成'); @@ -2282,9 +2294,6 @@ class FeedbackApp { } const sessionData = await response.json(); - console.log(`🔍 自動檢測獲取到會話數據:`, sessionData); - console.log(`🔍 當前記錄的會話 ID: ${this.lastKnownSessionId}`); - console.log(`🔍 API 返回的會話 ID: ${sessionData.session_id}`); // 檢查會話 ID 是否變化 if (sessionData.session_id && sessionData.session_id !== this.lastKnownSessionId) { @@ -2306,7 +2315,6 @@ class FeedbackApp { } }, 2000); } else { - console.log(`🔍 會話 ID 未變化,跳過更新`); this.updateAutoRefreshStatus('enabled'); } From 42dee74c8975644f645f51199c5f0fa05b98c4ab Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sun, 8 Jun 2025 00:52:30 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=E5=84=AA=E5=8C=96?= =?UTF-8?q?=20WebSocket=20=E9=80=A3=E6=8E=A5=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/web/main.py | 58 ++++-- .../web/routes/main_routes.py | 22 ++- .../web/static/js/app.js | 176 +++++++++++++++--- 3 files changed, 209 insertions(+), 47 deletions(-) diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index f4aa907..ac110d8 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -261,7 +261,7 @@ class WebUIManager: # 處理會話更新通知 if old_websocket: - # 有舊連接,立即發送會話更新通知 + # 有舊連接,立即發送會話更新通知並轉移連接 self._old_websocket_for_update = old_websocket self._new_session_for_update = session debug_log("已保存舊 WebSocket 連接,準備發送會話更新通知") @@ -269,10 +269,13 @@ class WebUIManager: # 立即發送會話更新通知 import asyncio try: - # 在後台任務中發送通知 + # 在後台任務中發送通知並轉移連接 asyncio.create_task(self._send_immediate_session_update()) except Exception as e: debug_log(f"創建會話更新任務失敗: {e}") + # 即使任務創建失敗,也要嘗試直接轉移連接 + session.websocket = old_websocket + debug_log("任務創建失敗,直接轉移 WebSocket 連接到新會話") self._pending_session_update = True else: # 沒有舊連接,標記需要發送會話更新通知(當新 WebSocket 連接建立時) @@ -505,8 +508,21 @@ class WebUIManager: old_websocket = self._old_websocket_for_update new_session = self._new_session_for_update - # 檢查舊連接是否仍然有效 - if old_websocket and not old_websocket.client_state.DISCONNECTED: + # 改進的連接有效性檢查 + websocket_valid = False + if old_websocket: + try: + # 檢查 WebSocket 連接狀態 + if hasattr(old_websocket, 'client_state'): + websocket_valid = old_websocket.client_state != old_websocket.client_state.DISCONNECTED + else: + # 如果沒有 client_state 屬性,嘗試發送測試消息來檢查連接 + websocket_valid = True + except Exception as check_error: + debug_log(f"檢查 WebSocket 連接狀態失敗: {check_error}") + websocket_valid = False + + if websocket_valid: try: # 發送會話更新通知 await old_websocket.send_json({ @@ -523,11 +539,18 @@ class WebUIManager: # 延遲一小段時間讓前端處理消息 await asyncio.sleep(0.2) + # 將 WebSocket 連接轉移到新會話 + new_session.websocket = old_websocket + debug_log("已將 WebSocket 連接轉移到新會話") + except Exception as send_error: debug_log(f"發送會話更新通知失敗: {send_error}") - - # 安全關閉舊連接 - await self._safe_close_websocket(old_websocket) + # 如果發送失敗,仍然嘗試轉移連接 + new_session.websocket = old_websocket + debug_log("發送失敗但仍轉移 WebSocket 連接到新會話") + else: + debug_log("舊 WebSocket 連接無效,設置待更新標記") + self._pending_session_update = True # 清理臨時變數 delattr(self, '_old_websocket_for_update') @@ -544,29 +567,24 @@ class WebUIManager: self._pending_session_update = True async def _safe_close_websocket(self, websocket): - """安全關閉 WebSocket 連接,避免事件循環衝突""" + """安全關閉 WebSocket 連接,避免事件循環衝突 - 僅在連接已轉移後調用""" if not websocket: return + # 注意:此方法現在主要用於清理,因為連接已經轉移到新會話 + # 只有在確認連接沒有被新會話使用時才關閉 try: # 檢查連接狀態 - if websocket.client_state.DISCONNECTED: + if hasattr(websocket, 'client_state') and websocket.client_state.DISCONNECTED: debug_log("WebSocket 已斷開,跳過關閉操作") return - # 嘗試正常關閉 - await asyncio.wait_for(websocket.close(code=1000, reason="會話更新"), timeout=2.0) - debug_log("已正常關閉舊 WebSocket 連接") + # 由於連接已轉移到新會話,這裡不再主動關閉 + # 讓新會話管理這個連接的生命週期 + 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}") + debug_log(f"檢查 WebSocket 連接狀態時發生錯誤: {e}") async def _check_active_tabs(self) -> bool: """檢查是否有活躍標籤頁 - 優先檢查全局狀態,回退到 API""" diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index 7830534..a1efb7b 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -158,9 +158,13 @@ def setup_routes(manager: 'WebUIManager'): return await websocket.accept() - session.websocket = websocket - debug_log(f"WebSocket 連接建立: 當前活躍會話") + # 檢查會話是否已有 WebSocket 連接 + if session.websocket and session.websocket != websocket: + debug_log("會話已有 WebSocket 連接,替換為新連接") + + session.websocket = websocket + debug_log(f"WebSocket 連接建立: 當前活躍會話 {session.session_id}") # 發送連接成功消息 try: @@ -198,7 +202,14 @@ def setup_routes(manager: 'WebUIManager'): while True: data = await websocket.receive_text() message = json.loads(data) - await handle_websocket_message(manager, session, message) + + # 重新獲取當前會話,以防會話已切換 + current_session = manager.get_current_session() + if current_session and current_session.websocket == websocket: + await handle_websocket_message(manager, current_session, message) + else: + debug_log("會話已切換或 WebSocket 連接不匹配,忽略消息") + break except WebSocketDisconnect: debug_log(f"WebSocket 連接正常斷開") @@ -208,8 +219,9 @@ def setup_routes(manager: 'WebUIManager'): debug_log(f"WebSocket 錯誤: {e}") finally: # 安全清理 WebSocket 連接 - if session.websocket == websocket: - session.websocket = None + current_session = manager.get_current_session() + if current_session and current_session.websocket == websocket: + current_session.websocket = None debug_log("已清理會話中的 WebSocket 連接") @manager.app.post("/api/save-settings") diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 762f427..c91257f 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -170,6 +170,13 @@ class FeedbackApp { this.heartbeatInterval = null; this.heartbeatFrequency = 30000; // 30秒 WebSocket 心跳 + // 新增:WebSocket 連接狀態管理 + this.connectionReady = false; + this.pendingSubmission = null; + this.connectionCheckInterval = null; + this.sessionUpdatePending = false; + this.reconnectDelay = 1000; // 重連延遲,會逐漸增加 + // UI 狀態 this.currentTab = 'feedback'; @@ -857,11 +864,11 @@ class FeedbackApp { } /** - * 檢查是否可以提交回饋 + * 檢查是否可以提交回饋(舊版本,保持兼容性) */ canSubmitFeedback() { - const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected; - console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, canSubmit=${canSubmit}`); + const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected && this.connectionReady; + console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, connectionReady=${this.connectionReady}, canSubmit=${canSubmit}`); return canSubmit; } @@ -1025,11 +1032,13 @@ class FeedbackApp { this.websocket.onopen = () => { this.isConnected = true; + this.connectionReady = false; // 等待連接確認 this.updateConnectionStatus('connected', '已連接'); console.log('WebSocket 連接已建立'); - // 重置重連計數器 + // 重置重連計數器和延遲 this.reconnectAttempts = 0; + this.reconnectDelay = 1000; // 開始 WebSocket 心跳 this.startWebSocketHeartbeat(); @@ -1042,6 +1051,23 @@ class FeedbackApp { console.log('🔄 WebSocket 重連後重置處理狀態'); this.setFeedbackState('waiting_for_feedback'); } + + // 如果有待處理的會話更新,處理它 + if (this.sessionUpdatePending) { + console.log('🔄 處理待處理的會話更新'); + this.sessionUpdatePending = false; + } + + // 如果有待提交的回饋,處理它 + if (this.pendingSubmission) { + console.log('🔄 處理待提交的回饋'); + setTimeout(() => { + if (this.connectionReady && this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }, 500); // 等待連接完全就緒 + } }; this.websocket.onmessage = (event) => { @@ -1055,6 +1081,7 @@ class FeedbackApp { this.websocket.onclose = (event) => { this.isConnected = false; + this.connectionReady = false; console.log('WebSocket 連接已關閉, code:', event.code, 'reason:', event.reason); // 停止心跳 @@ -1072,15 +1099,23 @@ class FeedbackApp { } else { this.updateConnectionStatus('disconnected', '已斷開'); + // 會話更新導致的正常關閉,立即重連 + if (event.code === 1000 && event.reason === '會話更新') { + console.log('🔄 會話更新導致的連接關閉,立即重連...'); + this.sessionUpdatePending = true; + setTimeout(() => { + this.setupWebSocket(); + }, 200); // 短延遲後重連 + } // 只有在非正常關閉時才重連 - if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { + else if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; - const delay = Math.min(3000 * this.reconnectAttempts, 15000); // 最大延遲15秒 - console.log(`${delay / 1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); + this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 15000); // 指數退避,最大15秒 + console.log(`${this.reconnectDelay / 1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); setTimeout(() => { console.log(`🔄 開始重連 WebSocket... (第${this.reconnectAttempts}次)`); this.setupWebSocket(); - }, delay); + }, this.reconnectDelay); } else if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('❌ 達到最大重連次數,停止重連'); this.showMessage('WebSocket 連接失敗,請刷新頁面重試', 'error'); @@ -1138,6 +1173,18 @@ class FeedbackApp { switch (data.type) { case 'connection_established': console.log('WebSocket 連接確認'); + this.connectionReady = true; + + // 如果有待提交的回饋,現在可以提交了 + if (this.pendingSubmission) { + console.log('🔄 連接就緒,提交待處理的回饋'); + setTimeout(() => { + if (this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }, 100); + } break; case 'heartbeat_response': // 心跳回應,更新標籤頁活躍狀態 @@ -1209,8 +1256,11 @@ class FeedbackApp { document.title = `MCP Feedback - ${projectName}`; } - // 使用局部更新替代整頁刷新 - this.refreshPageContent(); + // 確保 WebSocket 連接就緒 + this.ensureWebSocketReady(() => { + // 使用局部更新替代整頁刷新 + this.refreshPageContent(); + }); } else { // 如果沒有會話信息,仍然重置狀態 console.log('⚠️ 會話更新沒有包含會話信息,僅重置狀態'); @@ -1220,6 +1270,51 @@ class FeedbackApp { console.log('✅ 會話更新處理完成'); } + /** + * 確保 WebSocket 連接就緒 + */ + ensureWebSocketReady(callback, maxWaitTime = 5000) { + const startTime = Date.now(); + + const checkConnection = () => { + if (this.isConnected && this.connectionReady) { + console.log('✅ WebSocket 連接已就緒'); + if (callback) callback(); + return; + } + + const elapsed = Date.now() - startTime; + if (elapsed >= maxWaitTime) { + console.log('⚠️ WebSocket 連接等待超時,強制執行回調'); + if (callback) callback(); + return; + } + + // 如果連接斷開,嘗試重連 + if (!this.isConnected) { + console.log('🔄 WebSocket 未連接,嘗試重連...'); + this.setupWebSocket(); + } + + // 繼續等待 + setTimeout(checkConnection, 200); + }; + + checkConnection(); + } + + /** + * 檢查是否可以提交回饋 + */ + canSubmitFeedback() { + const canSubmit = this.isConnected && + this.connectionReady && + this.feedbackState === 'waiting_for_feedback'; + + console.log(`🔍 檢查提交權限: isConnected=${this.isConnected}, connectionReady=${this.connectionReady}, feedbackState=${this.feedbackState}, canSubmit=${canSubmit}`); + return canSubmit; + } + async refreshPageContent() { console.log('🔄 局部更新頁面內容...'); @@ -1684,22 +1779,46 @@ class FeedbackApp { // 檢查是否可以提交回饋 if (!this.canSubmitFeedback()) { - console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected); + console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected, '連接就緒:', this.connectionReady); 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.setupWebSocket(); + } else if (!this.isConnected || !this.connectionReady) { + // 收集回饋數據,等待連接就緒後提交 + const feedbackData = this.collectFeedbackData(); + if (feedbackData) { + this.pendingSubmission = feedbackData; + this.showMessage('WebSocket 連接中,回饋將在連接就緒後自動提交...', 'info'); + + // 確保 WebSocket 連接 + this.ensureWebSocketReady(() => { + if (this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }); + } } else { this.showMessage(`當前狀態不允許提交: ${this.feedbackState}`, 'warning'); } return; } + // 收集回饋數據並提交 + const feedbackData = this.collectFeedbackData(); + if (!feedbackData) { + return; + } + + this.submitFeedbackInternal(feedbackData); + } + + /** + * 收集回饋數據 + */ + collectFeedbackData() { // 根據當前佈局模式獲取回饋內容 let feedback = ''; if (this.layoutMode.startsWith('combined')) { @@ -1712,9 +1831,25 @@ class FeedbackApp { if (!feedback && this.images.length === 0) { this.showMessage('請提供回饋文字或上傳圖片', 'warning'); - return; + return null; } + return { + feedback: feedback, + images: [...this.images], // 創建副本 + settings: { + image_size_limit: this.imageSizeLimit, + enable_base64_detail: this.enableBase64Detail + } + }; + } + + /** + * 內部提交回饋方法 + */ + submitFeedbackInternal(feedbackData) { + console.log('📤 內部提交回饋...'); + // 設置處理狀態 this.setFeedbackState('processing'); @@ -1722,12 +1857,9 @@ class FeedbackApp { // 發送回饋 this.websocket.send(JSON.stringify({ type: 'submit_feedback', - feedback: feedback, - images: this.images, - settings: { - image_size_limit: this.imageSizeLimit, - enable_base64_detail: this.enableBase64Detail - } + feedback: feedbackData.feedback, + images: feedbackData.images, + settings: feedbackData.settings })); // 清空表單 From df5a1a610aa2b9ccf915aae2717f0fa5db820ae0 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sun, 8 Jun 2025 02:38:46 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E2=9C=A8=20=E5=9C=A8=20WSL=20=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E4=B8=8B=E9=A0=90=E8=A8=AD=E4=BD=BF=E7=94=A8=20Web=20?= =?UTF-8?q?UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/server.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mcp_feedback_enhanced/server.py b/src/mcp_feedback_enhanced/server.py index c1d5606..9f75b67 100644 --- a/src/mcp_feedback_enhanced/server.py +++ b/src/mcp_feedback_enhanced/server.py @@ -203,13 +203,21 @@ def is_remote_environment() -> bool: def can_use_gui() -> bool: """ 檢測是否可以使用圖形介面 - + + WSL 環境預設使用 Web UI,因為大多數 WSL 安裝都是 Linux 環境, + 沒有桌面應用支援,即使 PySide6 可以載入也應該使用 Web 介面。 + Returns: bool: True 表示可以使用 GUI,False 表示只能使用 Web UI """ if is_remote_environment(): return False - + + # WSL 環境預設使用 Web UI + if is_wsl_environment(): + debug_log("WSL 環境偵測到,預設使用 Web UI") + return False + try: from PySide6.QtWidgets import QApplication debug_log("成功載入 PySide6,可使用 GUI") @@ -552,7 +560,7 @@ async def interactive_feedback( # 使用統一錯誤處理,但不影響 JSON RPC 響應 error_id = ErrorHandler.log_error_with_context( e, - context={"operation": "回饋收集", "project_dir": project_dir}, + context={"operation": "回饋收集", "project_dir": project_directory}, error_type=ErrorType.SYSTEM ) From c9555a7132ae5f221e08858cc97ff5e7e8008d96 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sun, 8 Jun 2025 02:43:28 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=93=9D=20=E6=9B=B4=E6=96=B0=202.3.0?= =?UTF-8?q?=20=E7=9B=B8=E9=97=9C=E6=96=87=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 73 +++++++---- README.zh-CN.md | 61 ++++++---- README.zh-TW.md | 34 +++++- RELEASE_NOTES/CHANGELOG.en.md | 44 +++---- RELEASE_NOTES/CHANGELOG.zh-CN.md | 57 +++------ RELEASE_NOTES/CHANGELOG.zh-TW.md | 44 +++---- docs/en/images/ssh-remote-connect-url.png | Bin 0 -> 120104 bytes docs/en/images/ssh-remote-debug-port.png | Bin 0 -> 62330 bytes docs/en/images/ssh-remote-port-setting.png | Bin 0 -> 28552 bytes docs/en/ssh-remote/browser-launch-issues.md | 115 ++++++++++++++++++ docs/zh-CN/images/ssh-remote-connect-url.png | Bin 0 -> 120104 bytes docs/zh-CN/images/ssh-remote-debug-port.png | Bin 0 -> 62330 bytes docs/zh-CN/images/ssh-remote-port-setting.png | Bin 0 -> 28552 bytes .../zh-CN/ssh-remote/browser-launch-issues.md | 115 ++++++++++++++++++ docs/zh-TW/images/ssh-remote-connect-url.png | Bin 0 -> 120104 bytes docs/zh-TW/images/ssh-remote-debug-port.png | Bin 0 -> 62330 bytes docs/zh-TW/images/ssh-remote-port-setting.png | Bin 0 -> 28552 bytes .../zh-TW/ssh-remote/browser-launch-issues.md | 103 ++++++++++++++++ issues/WSL環境預設使用WebUI修復.md | 86 +++++++++++++ 19 files changed, 587 insertions(+), 145 deletions(-) create mode 100644 docs/en/images/ssh-remote-connect-url.png create mode 100644 docs/en/images/ssh-remote-debug-port.png create mode 100644 docs/en/images/ssh-remote-port-setting.png create mode 100644 docs/en/ssh-remote/browser-launch-issues.md create mode 100644 docs/zh-CN/images/ssh-remote-connect-url.png create mode 100644 docs/zh-CN/images/ssh-remote-debug-port.png create mode 100644 docs/zh-CN/images/ssh-remote-port-setting.png create mode 100644 docs/zh-CN/ssh-remote/browser-launch-issues.md create mode 100644 docs/zh-TW/images/ssh-remote-connect-url.png create mode 100644 docs/zh-TW/images/ssh-remote-debug-port.png create mode 100644 docs/zh-TW/images/ssh-remote-port-setting.png create mode 100644 docs/zh-TW/ssh-remote/browser-launch-issues.md create mode 100644 issues/WSL環境預設使用WebUI修復.md diff --git a/README.md b/README.md index 2b7795e..dc74269 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## 🎯 Core Concept -This is an [MCP server](https://modelcontextprotocol.io/) that establishes **feedback-oriented development workflows**, perfectly adapting to local, **SSH remote development environments**, and **WSL (Windows Subsystem for Linux) environments**. By guiding AI to confirm with users rather than making speculative operations, it can consolidate multiple tool calls into a single feedback-oriented request, dramatically reducing platform costs and improving development efficiency. +This is an [MCP server](https://modelcontextprotocol.io/) that establishes **feedback-oriented development workflows**, perfectly adapting to local, **SSH Remote environments** (Cursor SSH Remote, VS Code Remote SSH), and **WSL (Windows Subsystem for Linux) environments**. By guiding AI to confirm with users rather than making speculative operations, it can consolidate multiple tool calls into a single feedback-oriented request, dramatically reducing platform costs and improving development efficiency. **Supported Platforms:** [Cursor](https://www.cursor.com) | [Cline](https://cline.bot) | [Windsurf](https://windsurf.com) | [Augment](https://www.augmentcode.com) | [Trae](https://www.trae.ai) @@ -42,12 +42,19 @@ This is an [MCP server](https://modelcontextprotocol.io/) that establishes **fee - **Smart Detection**: Auto-select based on system language - **Live Switching**: Change language directly within interface -### ✨ WSL Environment Support (v2.2.5 New Feature) +### ✨ WSL Environment Support (v2.2.5) - **Auto Detection**: Intelligently identifies WSL (Windows Subsystem for Linux) environments - **Browser Integration**: Automatically launches Windows browser in WSL environments - **Multiple Launch Methods**: Supports `cmd.exe`, `powershell.exe`, `wslview` and other browser launch methods - **Seamless Experience**: WSL users can directly use Web UI without additional configuration +### 🌐 SSH Remote Environment Support (v2.3.0 New Feature) +- **Smart Detection**: Automatically identifies SSH Remote environments (Cursor SSH Remote, VS Code Remote SSH, etc.) +- **Browser Launch Guidance**: Provides clear solutions when browser cannot launch automatically +- **Port Forwarding Support**: Complete port forwarding setup guidance and troubleshooting +- **MCP Integration Optimization**: Improved integration with MCP system for more stable connection experience +- **Detailed Documentation**: [SSH Remote Environment Usage Guide](docs/en/ssh-remote/browser-launch-issues.md) + ## 🖥️ Interface Preview ### Qt GUI Interface (Refactored Version) @@ -134,6 +141,7 @@ For best results, add these rules to your AI assistant: |----------|---------|--------|---------| | `FORCE_WEB` | Force use Web UI | `true`/`false` | `false` | | `MCP_DEBUG` | Debug mode | `true`/`false` | `false` | +| `MCP_WEB_PORT` | Web UI port | `1024-65535` | `8765` | ### Testing Options ```bash @@ -178,20 +186,38 @@ uvx --with-editable . mcp-feedback-enhanced test --web # Test Web UI (auto co 📋 **Complete Version History:** [RELEASE_NOTES/CHANGELOG.en.md](RELEASE_NOTES/CHANGELOG.en.md) -### Latest Version Highlights (v2.2.5) -- ✨ **WSL Environment Support**: Added comprehensive support for WSL (Windows Subsystem for Linux) environments -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience +### Latest Version Highlights (v2.3.0) +- 🌐 **SSH Remote Environment Support**: Solved Cursor SSH Remote browser launch issues with clear usage guidance +- 🛡️ **Error Message Improvements**: Provides more user-friendly error messages and solution suggestions when errors occur +- 🧹 **Auto-cleanup Features**: Automatically cleans temporary files and expired sessions to keep the system tidy +- 📊 **Memory Monitoring**: Monitors memory usage to prevent system resource shortage +- 🔧 **Connection Stability**: Improved Web UI connection stability and error handling ## 🐛 Common Issues +### 🌐 SSH Remote Environment Issues +**Q: Browser cannot launch in SSH Remote environment** +A: This is normal behavior. SSH Remote environments have no graphical interface, requiring manual opening in local browser. For detailed solutions, see: [SSH Remote Environment Usage Guide](docs/en/ssh-remote/browser-launch-issues.md) + +**Q: Why am I not receiving new MCP feedback?** +A: There might be a WebSocket connection issue. **Solution**: Simply refresh the browser page. + +**Q: Why isn't MCP being called?** +A: Please confirm the MCP tool status shows green light. **Solution**: Toggle the MCP tool on/off repeatedly, wait a few seconds for system reconnection. + +**Q: Augment cannot start MCP** +A: **Solution**: Completely close and restart VS Code or Cursor, then reopen the project. + +### 🔧 General Issues **Q: Getting "Unexpected token 'D'" error** A: Debug output interference. Set `MCP_DEBUG=false` or remove the environment variable. **Q: Chinese character garbled text** A: Fixed in v2.0.3. Update to latest version: `uvx mcp-feedback-enhanced@latest` +**Q: Multi-screen window disappearing or positioning errors** +A: Fixed in v2.1.1. Go to "⚙️ Settings" tab, check "Always show window at primary screen center" to resolve. Especially useful for T-shaped screen arrangements and other complex multi-monitor configurations. + **Q: Image upload fails** A: Check file size (≤1MB) and format (PNG/JPG/GIF/BMP/WebP). @@ -218,21 +244,11 @@ uv cache clean ``` For detailed instructions, see: [Cache Management Guide](docs/en/cache-management.md) -**Q: Gemini Pro 2.5 cannot parse images** -A: Known issue. Gemini Pro 2.5 may not correctly parse uploaded image content. Testing shows Claude-4-Sonnet can properly analyze images. Recommend using Claude models for better image understanding capabilities. - -**Q: Multi-screen window positioning issues** -A: Fixed in v2.1.1. Go to "⚙️ Settings" tab, check "Always show window at primary screen center" to resolve window positioning issues. Especially useful for T-shaped screen arrangements and other complex multi-monitor configurations. - -**Q: Cannot launch browser in WSL environment** -A: v2.2.5 has added WSL environment support. If issues persist: -1. Confirm WSL version (WSL 2 recommended) -2. Check if Windows browser is properly installed -3. Try manual test: run `cmd.exe /c start https://www.google.com` in WSL -4. If `wslu` package is installed, you can also try the `wslview` command - -**Q: WSL environment misidentified as remote environment** -A: v2.2.5 has fixed this issue. WSL environments are now correctly identified and use Web UI with Windows browser launching, instead of being misidentified as remote environments. +**Q: AI models cannot parse images** +A: Various AI models (including Gemini Pro 2.5, Claude, etc.) may have instability in image parsing, sometimes correctly identifying and sometimes unable to parse uploaded image content. This is a known limitation of AI visual understanding technology. Recommendations: +1. Ensure good image quality (high contrast, clear text) +2. Try uploading multiple times, retries usually succeed +3. If parsing continues to fail, try adjusting image size or format ## 🙏 Acknowledgments @@ -245,4 +261,15 @@ If you find this useful, please: - 📱 [Follow the original author](https://x.com/fabiomlferreira) ### Design Inspiration -**sanshao85** - [mcp-feedback-collector](https://github.com/sanshao85/mcp-feedback-collector) \ No newline at end of file +**sanshao85** - [mcp-feedback-collector](https://github.com/sanshao85/mcp-feedback-collector) + +### Community Support +- **Discord:** [https://discord.gg/Gur2V67](https://discord.gg/Gur2V67) +- **Issues:** [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) + +## 📄 License + +MIT License - see [LICENSE](LICENSE) file for details + +--- +**🌟 Welcome to Star and share with more developers!** \ No newline at end of file diff --git a/README.zh-CN.md b/README.zh-CN.md index 20bdc0d..b94e59a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -8,7 +8,7 @@ ## 🎯 核心概念 -这是一个 [MCP 服务器](https://modelcontextprotocol.io/),建立**反馈导向的开发工作流程**,完美适配本地、**SSH 远程开发环境**与 **WSL (Windows Subsystem for Linux) 环境**。通过引导 AI 与用户确认而非进行推测性操作,可将多次工具调用合并为单次反馈导向请求,大幅节省平台成本并提升开发效率。 +这是一个 [MCP 服务器](https://modelcontextprotocol.io/),建立**反馈导向的开发工作流程**,完美适配本地、**SSH Remote 环境**(Cursor SSH Remote、VS Code Remote SSH)与 **WSL (Windows Subsystem for Linux) 环境**。通过引导 AI 与用户确认而非进行推测性操作,可将多次工具调用合并为单次反馈导向请求,大幅节省平台成本并提升开发效率。 **支持平台:** [Cursor](https://www.cursor.com) | [Cline](https://cline.bot) | [Windsurf](https://windsurf.com) | [Augment](https://www.augmentcode.com) | [Trae](https://www.trae.ai) @@ -42,12 +42,19 @@ - **智能检测**:根据系统语言自动选择 - **即时切换**:界面内可直接切换语言 -### ✨ WSL 环境支持(v2.2.5 新功能) +### ✨ WSL 环境支持(v2.2.5) - **自动检测**:智能识别 WSL (Windows Subsystem for Linux) 环境 - **浏览器整合**:WSL 环境下自动启动 Windows 浏览器 - **多种启动方式**:支持 `cmd.exe`、`powershell.exe`、`wslview` 等多种浏览器启动方法 - **无缝体验**:WSL 用户可直接使用 Web UI,无需额外配置 +### 🌐 SSH Remote 环境支持(v2.3.0 新功能) +- **智能检测**:自动识别 SSH Remote 环境(Cursor SSH Remote、VS Code Remote SSH 等) +- **浏览器启动指引**:当无法自动启动浏览器时,提供清晰的解决方案 +- **端口转发支持**:完整的端口转发设置指引和故障排除 +- **MCP 整合优化**:改善与 MCP 系统的整合,提供更稳定的连接体验 +- **详细文档**:[SSH Remote 环境使用指南](docs/zh-CN/ssh-remote/browser-launch-issues.md) + ## 🖥️ 界面预览 ### Qt GUI 界面(重构版) @@ -180,25 +187,43 @@ uvx --with-editable . mcp-feedback-enhanced test --web # 测试 Web UI (自 📋 **完整版本更新记录:** [RELEASE_NOTES/CHANGELOG.zh-CN.md](RELEASE_NOTES/CHANGELOG.zh-CN.md) -### 最新版本亮点(v2.2.5) -- ✨ **WSL 环境支持**: 新增 WSL (Windows Subsystem for Linux) 环境的完整支持 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 +### 最新版本亮点(v2.3.0) +- 🌐 **SSH Remote 环境支持**: 解决 Cursor SSH Remote 无法启动浏览器的问题,提供清晰的使用指引 +- 🛡️ **错误提示改善**: 当发生错误时,提供更友善的错误信息和解决建议 +- 🧹 **自动清理功能**: 自动清理临时文件和过期会话,保持系统整洁 +- 📊 **内存监控**: 监控内存使用情况,防止系统资源不足 +- 🔧 **连接稳定性**: 改善 Web UI 的连接稳定性和错误处理 ## 🐛 常见问题 +### 🌐 SSH Remote 环境问题 +**Q: SSH Remote 环境下浏览器无法启动** +A: 这是正常现象。SSH Remote 环境没有图形界面,需要手动在本地浏览器打开。详细解决方案请参考:[SSH Remote 环境使用指南](docs/zh-CN/ssh-remote/browser-launch-issues.md) + +**Q: 为什么没有接收到 MCP 新的反馈?** +A: 可能是 WebSocket 连接问题。**解决方法**:直接重新刷新浏览器页面。 + +**Q: 为什么没有调用出 MCP?** +A: 请确认 MCP 工具状态为绿灯。**解决方法**:反复开关 MCP 工具,等待几秒让系统重新连接。 + +**Q: Augment 无法启动 MCP** +A: **解决方法**:完全关闭并重新启动 VS Code 或 Cursor,重新打开项目。 + +### 🔧 一般问题 **Q: 出现 "Unexpected token 'D'" 错误** A: 调试输出干扰。设置 `MCP_DEBUG=false` 或移除该环境变量。 **Q: 中文字符乱码** A: 已在 v2.0.3 修复。更新到最新版本:`uvx mcp-feedback-enhanced@latest` +**Q: 多屏幕环境下窗口消失或定位错误** +A: 已在 v2.1.1 修复。进入「⚙️ 设置」标签页,勾选「总是在主屏幕中心显示窗口」即可解决。特别适用于 T 字型屏幕排列等复杂多屏幕配置。 + **Q: 图片上传失败** A: 检查文件大小(≤1MB)和格式(PNG/JPG/GIF/BMP/WebP)。 **Q: Web UI 无法启动** -A: 设置 `FORCE_WEB=true` 或检查火墙设定。 +A: 设置 `FORCE_WEB=true` 或检查防火墙设置。 **Q: UV Cache 占用过多磁盘空间** A: 由于频繁使用 `uvx` 命令,cache 可能会累积到数十 GB。建议定期清理: @@ -220,21 +245,11 @@ uv cache clean ``` 详细说明请参考:[Cache 管理指南](docs/zh-CN/cache-management.md) -**Q: Gemini Pro 2.5 无法解析图片** -A: 已知问题,Gemini Pro 2.5 可能无法正确解析上传的图片内容。实测 Claude-4-Sonnet 可以正常解析图片。建议使用 Claude 模型获得更好的图片理解能力。 - -**Q: 多屏幕视窗定位问题** -A: 已在 v2.1.1 修复。进入「⚙️ 设置」标签页,勾选「总是在主屏幕中心显示窗口」即可解决窗口定位问题。特别适用于 T 字型屏幕排列等复杂多屏幕配置。 - -**Q: WSL 环境下无法启动浏览器** -A: v2.2.5 已新增 WSL 环境支持。如果仍有问题: -1. 确认 WSL 版本(建议使用 WSL 2) -2. 检查 Windows 浏览器是否正常安装 -3. 尝试手动测试:在 WSL 中执行 `cmd.exe /c start https://www.google.com` -4. 如果安装了 `wslu` 套件,也可尝试 `wslview` 命令 - -**Q: WSL 环境被误判为远程环境** -A: v2.2.5 已修复此问题。WSL 环境现在会被正确识别并使用 Web UI 配合 Windows 浏览器启动,而不会被误判为远程环境。 +**Q: AI 模型无法解析图片** +A: 各种 AI 模型(包括 Gemini Pro 2.5、Claude 等)在图片解析上可能存在不稳定性,表现为有时能正确识别、有时无法解析上传的图片内容。这是 AI 视觉理解技术的已知限制。建议: +1. 确保图片质量良好(高对比度、清晰文字) +2. 多尝试几次上传,通常重试可以成功 +3. 如持续无法解析,可尝试调整图片大小或格式 ## 🙏 致谢 diff --git a/README.zh-TW.md b/README.zh-TW.md index 4320f90..0794410 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -42,12 +42,19 @@ - **智能偵測**:根據系統語言自動選擇 - **即時切換**:介面內可直接切換語言 -### ✨ WSL 環境支援(v2.2.5 新功能) +### ✨ WSL 環境支援(v2.2.5) - **自動檢測**:智能識別 WSL (Windows Subsystem for Linux) 環境 - **瀏覽器整合**:WSL 環境下自動啟動 Windows 瀏覽器 - **多種啟動方式**:支援 `cmd.exe`、`powershell.exe`、`wslview` 等多種瀏覽器啟動方法 - **無縫體驗**:WSL 用戶可直接使用 Web UI,無需額外配置 +### 🌐 SSH Remote 環境支援(v2.3.0 新功能) +- **智能檢測**:自動識別 SSH Remote 環境(Cursor SSH Remote、VS Code Remote SSH 等) +- **瀏覽器啟動指引**:當無法自動啟動瀏覽器時,提供清晰的解決方案 +- **端口轉發支援**:完整的端口轉發設定指引和故障排除 +- **MCP 整合優化**:改善與 MCP 系統的整合,提供更穩定的連接體驗 +- **詳細文檔**:[SSH Remote 環境使用指南](docs/zh-TW/ssh-remote/browser-launch-issues.md) + ## 🖥️ 介面預覽 ### Qt GUI 介面(重構版) @@ -180,14 +187,29 @@ uvx --with-editable . mcp-feedback-enhanced test --web # 測試 Web UI (自 📋 **完整版本更新記錄:** [RELEASE_NOTES/CHANGELOG.zh-TW.md](RELEASE_NOTES/CHANGELOG.zh-TW.md) -### 最新版本亮點(v2.2.5) -- ✨ **WSL 環境支援**: 新增 WSL (Windows Subsystem for Linux) 環境的完整支援 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 +### 最新版本亮點(v2.3.0) +- 🌐 **SSH Remote 環境支援**: 解決 Cursor SSH Remote 無法啟動瀏覽器的問題,提供清晰的使用指引 +- 🛡️ **錯誤提示改善**: 當發生錯誤時,提供更友善的錯誤訊息和解決建議 +- 🧹 **自動清理功能**: 自動清理臨時文件和過期會話,保持系統整潔 +- 📊 **記憶體監控**: 監控記憶體使用情況,防止系統資源不足 +- 🔧 **連線穩定性**: 改善 Web UI 的連線穩定性和錯誤處理 ## 🐛 常見問題 +### 🌐 SSH Remote 環境問題 +**Q: SSH Remote 環境下瀏覽器無法啟動** +A: 這是正常現象。SSH Remote 環境沒有圖形界面,需要手動在本地瀏覽器開啟。詳細解決方案請參考:[SSH Remote 環境使用指南](docs/zh-TW/ssh-remote/browser-launch-issues.md) + +**Q: 為什麼沒有接收到 MCP 新的反饋?** +A: 可能是 WebSocket 連接問題。**解決方法**:直接重新整理瀏覽器頁面。 + +**Q: 為什麼沒有呼叫出 MCP?** +A: 請確認 MCP 工具狀態為綠燈。**解決方法**:反覆開關 MCP 工具,等待幾秒讓系統重新連接。 + +**Q: Augment 無法啟動 MCP** +A: **解決方法**:完全關閉並重新啟動 VS Code 或 Cursor,重新開啟專案。 + +### 🔧 一般問題 **Q: 出現 "Unexpected token 'D'" 錯誤** A: 調試輸出干擾。設置 `MCP_DEBUG=false` 或移除該環境變數。 diff --git a/RELEASE_NOTES/CHANGELOG.en.md b/RELEASE_NOTES/CHANGELOG.en.md index 596f6f3..ae884a4 100644 --- a/RELEASE_NOTES/CHANGELOG.en.md +++ b/RELEASE_NOTES/CHANGELOG.en.md @@ -2,38 +2,32 @@ This document records all version updates for **MCP Feedback Enhanced**. -## [v2.2.5] - WSL Environment Support & Cross-Platform Enhancement -# Release v2.2.5 - WSL Environment Support & Cross-Platform Enhancement +## [v2.3.0] - System Stability & Resource Management Enhancement -## 🌟 Highlights -This version introduces comprehensive support for WSL (Windows Subsystem for Linux) environments, enabling WSL users to seamlessly use this tool with automatic Windows browser launching, significantly improving cross-platform development experience. +### 🌟 Highlights +This version focuses on improving system stability and user experience, particularly solving the browser launch issue in Cursor SSH Remote environments. -## ✨ New Features -- 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🔧 **Cross-Platform Testing Enhancement**: Test functionality integrates WSL detection for improved test coverage +### ✨ New Features +- 🌐 **SSH Remote Environment Support**: Solved Cursor SSH Remote browser launch issues with clear usage guidance +- 🛡️ **Error Message Improvements**: Provides more user-friendly error messages and solution suggestions when errors occur +- 🧹 **Auto-cleanup Features**: Automatically cleans temporary files and expired sessions to keep the system tidy +- 📊 **Memory Monitoring**: Monitors memory usage to prevent system resource shortage -## 🚀 Improvements -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 📊 **System Information Enhancement**: System information tool now displays WSL environment status -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience +### 🚀 Improvements +- 💾 **Resource Management Optimization**: Better system resource management for improved performance +- 🔧 **Enhanced Error Handling**: Provides clearer explanations and solutions when problems occur +- 🌐 **Connection Stability**: Improved Web UI connection stability +- 🖼️ **Image Upload Optimization**: Enhanced stability of image upload functionality -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) +### 🐛 Bug Fixes +- 🌐 **Connection Issues**: Fixed WebSocket connection related problems +- 🔄 **Session Management**: Fixed session state tracking issues +- 🖼️ **Image Processing**: Fixed event handling issues during image upload --- +## [v2.2.5] - WSL Environment Support & Cross-Platform Enhancement + ### ✨ New Features - 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic - 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods diff --git a/RELEASE_NOTES/CHANGELOG.zh-CN.md b/RELEASE_NOTES/CHANGELOG.zh-CN.md index cfc7ea7..9a18a21 100644 --- a/RELEASE_NOTES/CHANGELOG.zh-CN.md +++ b/RELEASE_NOTES/CHANGELOG.zh-CN.md @@ -2,56 +2,27 @@ 本文件记录了 **MCP Feedback Enhanced** 的所有版本更新内容。 -## [v2.2.5] - WSL 环境支持与跨平台增强 -# Release v2.2.5 - WSL 环境支持与跨平台增强 +## [v2.3.0] - 系统稳定性与资源管理增强 -## 🌟 亮点 -本版本新增了 WSL (Windows Subsystem for Linux) 环境的完整支持,让 WSL 用户能够无缝使用本工具并自动启动 Windows 浏览器,大幅提升跨平台开发体验。 - -## ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 - -## 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) - ---- +### 🌟 亮点 +本版本专注于提升系统稳定性和使用体验,特别解决了 Cursor SSH Remote 环境下无法启动浏览器的问题。 ### ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 +- 🌐 **SSH Remote 环境支持**: 解决 Cursor SSH Remote 无法启动浏览器的问题,提供清晰的使用指引 +- 🛡️ **错误提示改善**: 当发生错误时,提供更友善的错误信息和解决建议 +- 🧹 **自动清理功能**: 自动清理临时文件和过期会话,保持系统整洁 +- 📊 **内存监控**: 监控内存使用情况,防止系统资源不足 ### 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - ---- - -## [v2.2.4] - GUI 体验优化与问题修复 +- 💾 **资源管理优化**: 更好地管理系统资源,提升运行效率 +- 🔧 **错误处理增强**: 遇到问题时提供更清楚的说明和解决方案 +- 🌐 **连接稳定性**: 改善 Web UI 的连接稳定性 +- 🖼️ **图片上传优化**: 改善图片上传功能的稳定性 ### 🐛 问题修复 -- 🖼️ **图片重复粘贴修复**: 解决 GUI 界面中使用 Ctrl+V 复制粘贴图片时出现重复粘贴的问题 -- 🌐 **语系切换修复**: 修复图片设定区域在语言切换时文字没有正确翻译的问题 -- 📝 **字体可读性改善**: 调整图片设定区域的字体大小,提升文字可读性 +- 🌐 **连接问题**: 修复 WebSocket 连接的相关问题 +- 🔄 **会话管理**: 修复会话状态跟踪的问题 +- 🖼️ **图片处理**: 修复图片上传时的事件处理问题 --- diff --git a/RELEASE_NOTES/CHANGELOG.zh-TW.md b/RELEASE_NOTES/CHANGELOG.zh-TW.md index 8f04371..bfaf78e 100644 --- a/RELEASE_NOTES/CHANGELOG.zh-TW.md +++ b/RELEASE_NOTES/CHANGELOG.zh-TW.md @@ -2,38 +2,32 @@ 本文件記錄了 **MCP Feedback Enhanced** 的所有版本更新內容。 -## [v2.2.5] - WSL 環境支援與跨平台增強 -# Release v2.2.5 - WSL 環境支援與跨平台增強 +## [v2.3.0] - 系統穩定性與資源管理增強 -## 🌟 亮點 -本版本新增了 WSL (Windows Subsystem for Linux) 環境的完整支援,讓 WSL 用戶能夠無縫使用本工具並自動啟動 Windows 瀏覽器,大幅提升跨平台開發體驗。 +### 🌟 亮點 +本版本專注於提升系統穩定性和使用體驗,特別解決了 Cursor SSH Remote 環境下無法啟動瀏覽器的問題。 -## ✨ 新功能 -- 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🔧 **跨平台測試增強**: 測試功能整合 WSL 檢測,提升測試覆蓋率 +### ✨ 新功能 +- 🌐 **SSH Remote 環境支援**: 解決 Cursor SSH Remote 無法啟動瀏覽器的問題,提供清晰的使用指引 +- 🛡️ **錯誤提示改善**: 當發生錯誤時,提供更友善的錯誤訊息和解決建議 +- 🧹 **自動清理功能**: 自動清理臨時文件和過期會話,保持系統整潔 +- 📊 **記憶體監控**: 監控記憶體使用情況,防止系統資源不足 -## 🚀 改進功能 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 📊 **系統資訊增強**: 系統資訊工具新增 WSL 環境狀態顯示 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 +### 🚀 改進功能 +- 💾 **資源管理優化**: 更好地管理系統資源,提升運行效率 +- 🔧 **錯誤處理增強**: 遇到問題時提供更清楚的說明和解決方案 +- 🌐 **連線穩定性**: 改善 Web UI 的連線穩定性 +- 🖼️ **圖片上傳優化**: 改善圖片上傳功能的穩定性 -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) +### 🐛 問題修復 +- 🌐 **連線問題**: 修復 WebSocket 連線的相關問題 +- 🔄 **會話管理**: 修復會話狀態追蹤的問題 +- 🖼️ **圖片處理**: 修復圖片上傳時的事件處理問題 --- +## [v2.2.5] - WSL 環境支援與跨平台增強 + ### ✨ 新功能 - 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 - 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 diff --git a/docs/en/images/ssh-remote-connect-url.png b/docs/en/images/ssh-remote-connect-url.png new file mode 100644 index 0000000000000000000000000000000000000000..7c67e886bdbdf7b7b2d914a9a82509bc21508fad GIT binary patch literal 120104 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%0AE4Rju1zc51SpXOku^r5brDI^ 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 TS6<=!fXmX<)}+?R>(74x(*F8m literal 0 HcmV?d00001 diff --git a/docs/en/images/ssh-remote-port-setting.png b/docs/en/images/ssh-remote-port-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..80a64bc28451fd063cd8a01f587e5a99cdddbb04 GIT binary patch literal 28552 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 literal 0 HcmV?d00001 diff --git a/docs/en/ssh-remote/browser-launch-issues.md b/docs/en/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..cbe0d30 --- /dev/null +++ b/docs/en/ssh-remote/browser-launch-issues.md @@ -0,0 +1,115 @@ +# SSH Remote Environment Browser Launch Issues Solution + +## Problem Description + +When using MCP Feedback Enhanced in SSH Remote environments (such as Cursor SSH Remote, VS Code Remote SSH, etc.), you may encounter the following issues: + +- 🚫 Browser cannot launch automatically +- ❌ "Unable to launch browser" error message +- 🔗 Web UI cannot open in local browser + +## Root Cause Analysis + +SSH Remote environment limitations: +1. **Display Environment Isolation**: Remote server has no graphical interface environment +2. **Network Isolation**: Remote ports cannot be directly accessed locally +3. **No Browser Available**: Remote environments typically don't have browsers installed + +## Solution + +### Step 1: Configure Port (Optional) + +MCP Feedback Enhanced uses port **8765** by default, but you can customize the port: + +![Port Settings](../images/ssh-remote-port-setting.png) + +### Step 2: Wait for MCP Call + +**Important**: Do not manually start the Web UI. Instead, wait for the AI model to call the MCP tool to automatically start it. + +When the AI model calls the `interactive_feedback` tool, the system will automatically start the Web UI. + +### Step 3: Check Port and Connect + +If the browser doesn't launch automatically, you need to manually connect to the Web UI: + +#### Method 1: Check Port Forwarding +Check your SSH Remote environment's port forwarding settings to find the corresponding local port: + +![Connect to URL](../images/ssh-remote-connect-url.png) + +#### Method 2: Use Debug Mode +Enable Debug mode in your IDE, select "Output" → "MCP Log" to see the Web UI URL: + +![Debug Mode Port View](../images/ssh-remote-debug-port.png) + +### Step 4: Open in Local Browser + +1. Copy the URL (usually `http://localhost:8765` or another port) +2. Paste and open in your local browser +3. Start using the Web UI for feedback + +## Port Forwarding Setup + +### VS Code Remote SSH +1. Press `Ctrl+Shift+P` in VS Code +2. Type "Forward a Port" +3. Enter the port number (default 8765) +4. Access `http://localhost:8765` in your local browser + +### Cursor SSH Remote +1. Check Cursor's port forwarding settings +2. Manually add port forwarding rule (port 8765) +3. Access the forwarded port in your local browser + +## Important Reminders + +### ⚠️ Do Not Start Manually +**Do NOT** manually execute commands like `uvx mcp-feedback-enhanced test --web`, as this cannot integrate with the MCP system. + +### ✅ Correct Process +1. Wait for AI model to call MCP tool +2. System automatically starts Web UI +3. Check port forwarding or Debug logs +4. Open corresponding URL in local browser + +## Frequently Asked Questions + +### Q: Why can't the browser launch automatically in SSH Remote environment? +A: SSH Remote environment is headless with no graphical interface, so browsers cannot be launched directly. You need to access through port forwarding in your local browser. + +### Q: How to confirm if Web UI started successfully? +A: Check IDE's Debug output or MCP Log. If you see "Web UI started" message, it means successful startup. + +### Q: What if the port is occupied? +A: Modify the port number in MCP settings, or wait for the system to automatically select another available port. + +### Q: Can't find port forwarding settings? +A: Check your SSH Remote tool documentation, or use Debug mode to view the URL in MCP Log. + +### Q: Why am I not receiving new MCP feedback? +A: There might be a WebSocket connection issue. **Solution**: Simply refresh the browser page to re-establish the WebSocket connection. + +### Q: Why isn't MCP being called? +A: Please confirm the MCP tool status shows green light (indicating normal operation). **Solution**: +- Check the MCP tool status indicator in your IDE +- If not green, try toggling the MCP tool on/off repeatedly +- Wait a few seconds for the system to reconnect + +### Q: Why can't Augment start MCP? +A: Sometimes errors may prevent the MCP tool from showing green status. **Solution**: +- Completely close and restart VS Code or Cursor +- Reopen the project +- Wait for MCP tool to reload and show green light + +## v2.3.0 Improvements + +Improvements for SSH Remote environments in this version: +- ✅ Automatic SSH Remote environment detection +- ✅ Clear guidance when browser cannot launch +- ✅ Display correct access URL +- ✅ Improved error messages and solution suggestions + +## Related Resources + +- [Main Documentation](../../README.md) diff --git a/docs/zh-CN/images/ssh-remote-connect-url.png b/docs/zh-CN/images/ssh-remote-connect-url.png new file mode 100644 index 0000000000000000000000000000000000000000..7c67e886bdbdf7b7b2d914a9a82509bc21508fad GIT binary patch literal 120104 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%0AE4Rju1zc51SpXOku^r5brDI^ zLhC1fs@>KC1yMCVD#B+T`OQBzLud0&mY7wHu6K%5*z-Vo_jV-A z4^M#b($a!M^#8jcGg(XCCLf%ME9^ZGVJS8v?x?g6aOGI!z7YAzh|m{CAW@lJS^pU1 zxN*lM=Kw)o|C!oKs|L!89f%B##P`@7;HxvTTRX*XwEajI)2kWb}YksN~ zGp8%Z#<%M~pwZhTSSf->+2?yR5J2Pn-v*jbKC&LKidgS^^z5LN|I@I^2V^M2fGeh~ zh;t;k541-eY|tAm1c){LKtEVsrCqzG7R+I->X(!BaslC%3~hNsi^I(8!%lDiF28Z& z|D4}Y`493Nl@lyeKke#2f9<^UUwB#stN-e02?p3orl*UodCLoM3S!@_)F5m0&z;ok z-0bfGw*);hhCK-B_xUV%u{GNkFMqJ9^j7W`N33>(dVnP>5GHPWu5SP0<_4|2S#0&X zuXuhhkg8|@hA2-?$P|4akFHV0r;WP1#)+JHfSqyC!^bd(-1@6y{R8bw2lcGTrNVS8 z&ETy?gNU`n&~-J1S|UhC#4BE#Wc?9$uQIX*c3l+Ed5@J)3U;ZI@ zE({XrYU}#Y7i*us2%tdp07Av=G^w`XtOG`;?q!l=8hqBvO}O|!DRfhIg`wA732hIQZ%@bJU5b*1fLiy#WpK!<+PJE4Xg+O%)p;+y-f zIX7=4#T7QF?xUvY*#M84KX+72Z8TkZ@gHLw6st#s=a$Oc*(3qGX$$K$JFR)HJF?<9 zZM|GqH#q4GhvF1KbMCaDyK${l?uqc}}`->6<~y zstRChKzaD{rUoEG;}17hNuQoEO?!pBLSDRIsqjVrjK@zhpiFI24iFP=QOZVHi{{cj z!rpk$m9}aft?;nm(7KVXrSg&Pum5idox?37m`9`ZuRmwMM?|E6-8kZVgR>=$ahogE z2BtiMvOw&Aszew)#G(>sbN7P+2_8Zge_>f_Tn1*($KPJ1J$8Bdpw=H=>39C_C9Y>0 z&ga2HH;B7OjxhevQNML3kET#;zyf3e)5y{kGrk%5NH8o^^djd98uiGg9q@ z*+=80*K%d0*^ixja^(2sBS&d&9yxaI0`mB|0p2?IK&jt;`lI zObWAUl%uC2@U-xw+&}69;WrP|LqY23nH77^N`&cP(enZ4#nMPzOu4Ls-AEAVsLkS?XI>@ADrE0L;Ur?wbxGrCl$oi<6O%f6>OyB1M9_3f09LGv^L zR^DugJKeU2?AWkq+ceiD=31E-?wmQ%>;4L-(=>U_uS;_^{K)a=T;aFQ56BZksxKPm zHm96Y0}a&9IRTc?` z6)_|!^zL^z5VUQ{Iqb8=>Q>G-v)EzvB>rQ{d2ixwU(n*HZNskj5;PY zIwB%S1*#iajGy-*l|jo(6%NEJb^EAh0*|b`Ik(`l_tM?B?zRUck9)slhJl-$1I|u* z8?zk0d{u_#bbV4NsazgCS9}1|umfmE(=)6OobT;}3+Lv{2R0ArdYV6e-)UEb9%P3M z=r|6&yF?74DgqAKFiZLh}upW3c7s;Q-G3#fpITtGww42S|syGpxAQ##TVrAfUM36Q{* z62t_Mdnp3aOX!FaBO)LzA<|SpN+8h)fdm2;0*01LNhqPb$G7<|UF-YzonL32nc1^v z&N_4UJhS(H7DAJ~VUYt)2G}7cN*e^FpoPP2A6zMxM!Fj`JNY~*4@su+5`XKGG6=c` zB+YJEXpDv1X5d&^Cs(Jfu*d>m?~bdvmZ$26rgnR_PdnHWh;HqOyS=m_kYnk%9=uP; zn;ADF0Z@z1c6A7j8;p?}Z1SDXRni+R4I_5_@X2}eqmjuVB;QR>J$BR#rLj0R!31qp zE&QyasY=hUkW`zEarS`+e!l!AvR1Q} zS__<|b-2#^%c?fSJlgqdoiNgtQ97I)8}fXypH)}Xq3iFXz_PdZ*U@b`!o~OCFt@Rz zMP1Zw8&KS$0D~zVS>p0?O?)O@mkF-sS4ywR?3g7+Y8$!jvPDPv7&mUnY0BI;<@V+= ztAXRVUbowM=BG(n$@=h0=D^BmBBQ$zX_>I~kexpoDHv$jRT&D`4ia5DrbjTYt_z_! zdt>txto&CEz;78dPV41BjvKGK*4ew8iwZ^Hr%he%Q)8`!h4bvxnL47!$lDQv#+}CX zm$o~&(Iusn=TuJ2op%mTBIgqou_S*OLxs76Mqr8Yma3h(YST+2ZlfF5l|gCcIE}}c z@%Iy~?w5>;%|I-AF0r26fJy`Y4q zPHQ{Z)e6Fe5uLZSD7Fh{eJp*J;=~+E0mCz--5yA)<0SO(ujAX7gl4A3S741zaMrv~ zflE$CaQ>Gqd_0u8gO&=aZD;n{oc^%eZ}C{kXJoZgn`3>k4O-apHdw+T^BPvMH9iyb z=*uexx>+TXy@>Uzjh&sE@vN%>=gV5js^Fgs-&!gYY!7l}eOh6ayO@d4A<^K3xTD=g zGiPT7U@mu5MP#5kRs+UMT3ZUu; zIv29u@$$@86&T~I6m9Q;rQ4SO0nWRxDyCb{)p5(nKBcatGoEVQsZX9P7d=Du58W|A zLlK0f5`7)v;_6iASTv5fnTj~Lc#@GIAg)zSJPF^f$0W0)a&j||G-t6(>lf>bWopDo z9qzb3G~1nYUz@>mqWr_;mz9;Bz5|LLv0qpp13R5Jt1t>pMFVx!YKzS>l*n1c8+T@t zhX$*nIU)SpFG1ng%7ir_Ud_kt=-oxDpdpHXxt}U-+Fm`1*dHicDuOTN3+NOb9@M~J zHXn+SX9U|Ge?I0i6#kqotpDbnnqO5bCRNIxV=3Xy%5T&(S+d@ zZ}wN`r&w@u_&c1l9^FvdzLEfVoeLGjgaVYZuyH>&R-g1iuU6{s=pb9W#-kHvvh;wtI<5@LTXacpI-sC`_PY>>oT){aSD>y2*$=)T4>$Xg53uL$Mf1_s2X zK9Y!X?+j=z#Ei$;U^+5TD;QC69nZfP*XF;}Yhs()gLd~PtscPNo=;+GE2&PzL@b`p zliCiF!&Gnrh5VF-Wwf9HI;DJ!tCi=)WK{!3D=h?Q#l`oASjK1T-?M`eO(xFNcL65X zC#O7(s5Xka9Qx@nv=Q|-dBd2%tMNuf&pxDVt`AYgp25Ww_xV$W~ zj1sY38_l*bY@7S^5dy2pBs<0iUzOuEJHsgE`fLt=xUU=tA$Jg_}9+pN}aV{ zVDkI;Ygt_@&14Hn3Ydn~NXy2FrUbXPg~DP(lS&=le{|NjSe{AM()Mb&hG;dXTyWW( zh?w~(AuxULgbq0HjyjAdOLC;@c89jkBmk#y%QhGvI*3*uzjJqm{<_bXVZop z4pFR0tPin@wO*Q@>u!##$M`Q4=mS*I7(;_lws+%Vm&y#;oS<#Kgl#DVJPF@yKM};U zXgc{Zy22~8lB)!F;_Z(YfnR(B-{An+o#E?tMYnx=3zKu?3zJSF`UcAlr<6A-BI^DG zDIndcvH zlRNNuyR>mUC3hmuu44;*BOX|3*wkGXDkW4ww?|6A*bt5ou!9 z4A~B^5L54XhlzeI$$YNmYWP*KEaIB(``l_!4G2u?AyqC3-WIaiikIiPSj8J_{RykD zWj9XjGaxoMQaf~S6+h1ygJ*v!ZT09%gag*-C&2mt5TmS2V6cNjxWEji`UbZx%ZI(; z@0FCiU3QVR+G9j@OdATBbeh!^o_DBrY!0k;wCKH9{O;l4j-C2OHqVO^*MxZED;DVZ zM9IO{zQ;Q%hOuJ0eOC^ZjD>m>qotcB*k7~di)Phc3#t^`NT0~_`QRvCwy%NPd@1XQ zDNOSX6EweM54vzn#Ef7jzPvHeBs%UWY9X(6cMb$01Bal~47CjG`;lR;TSYvCP*KsM zt=t&-_2ZnVc-sIZhZe>(J{JhVXTN8byi)~YP5mstwqsbG#BRAQsayHQW7$LprOL3y zhey3?qgQ!nDlP|r?^Q2nyt`+p{dFj>T=ZCjyAX@3LeHCnvof)=;Oi|2Qs6h*$y-1+ zeO4x~C(JdB3kSHE1+(nt+WvCWmBpvI3d8rcUb6~kpyC=bqxWaxa23qTot-NU)~yWpKMl1 zKl@Ru&Ne+z9zliISl%&qS%4Cqq7fA9d;ElfEVzLliE-+U``z@n*xm6V>HZ8B zRi6aK#55f^$M#Eg+jAg^)3;UVkyTr@tksf`zDNxxL&962P8FuN=FdjIF=9B?P`6Nx zIMc_X$)?kuA>H(+CtYfd_i@W*yvh_K33N2RYH1&;Rd?sC;D=Tq>+lyHa?+{x@dr;- zq^plSMQROSATi88`*zzdWwDXi{;qN(;Ty44Pu&hJl&P=jQ!_JdDT^!FR$0xwr}}qp zhf=UjG@|w~dU!yk4Vhe|AI>&7Tj_Utj}ft6ko<)WU8Lo+SOaf6ix;tyG_3%8rAlCF zh6;{gi8dZgauw}N%rV!B-<=FA4TQv3)qxCQ(&Ks%-^u7>3bS1#*pyIM6dLiGoK$+_ zHg5uM-89(g5B#z*Z!3MS)%U6=sjMGd3j0_qOviDBD`sS`gl+bo%Bt8sr6uf2+8xbB z*Hlycv!#g6Qxc*xnnpJ}kPXqc?MO_zAL!~zTEvujlc*B)ca(f*$sg(N8JqSC?bTwW z+Nm&4xxO^rD_cv!p@Z%7)CLP)z%)m28{1n59c9O*15N_4ewm<$EkFn>@Z}$m3%;)$ z`|;#Cb0v+iH;pqdZ#~i&$w%iqp%?loCqx1Wj*DfXHBQK}Y|)G}QF)ak26!(N-sar! zm_reoJQXr0yyAWLv8xyf{trcXFDUOfIo!PVUU%)=$N>JGHxmJ~DGC!$pM z;atku-=^w&m)~xm*p&xhAe4!F8)K6n(Az?%e<=T^Xg5C~^vI8c{6B>LLfZIAuS*Z_ z0l^OYj#eW{f{>l3eG@RNvN6ai#J^`5EyPg(-hzo743 z{YTo-Ux2`fSEtVY7uET171=a^BlO2vI>aYaDd3fL^vCBwr*C=x|K8<>(bitvF-fJY TS6<=!fXmX<)}+?R>(74x(*F8m literal 0 HcmV?d00001 diff --git a/docs/zh-CN/images/ssh-remote-port-setting.png b/docs/zh-CN/images/ssh-remote-port-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..80a64bc28451fd063cd8a01f587e5a99cdddbb04 GIT binary patch literal 28552 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 literal 0 HcmV?d00001 diff --git a/docs/zh-CN/ssh-remote/browser-launch-issues.md b/docs/zh-CN/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..ae6e539 --- /dev/null +++ b/docs/zh-CN/ssh-remote/browser-launch-issues.md @@ -0,0 +1,115 @@ +# SSH Remote 环境浏览器启动问题解决方案 + +## 问题描述 + +在 SSH Remote 环境(如 Cursor SSH Remote、VS Code Remote SSH 等)中使用 MCP Feedback Enhanced 时,可能会遇到以下问题: + +- 🚫 浏览器无法自动启动 +- ❌ 显示「无法启动浏览器」错误 +- 🔗 Web UI 无法在本地浏览器中打开 + +## 原因分析 + +SSH Remote 环境的限制: +1. **显示环境隔离**: 远程服务器没有图形界面环境 +2. **网络隔离**: 远程端口无法直接在本地访问 +3. **浏览器不存在**: 远程环境通常没有安装浏览器 + +## 解决方案 + +### 步骤一:设置端口(可选) + +MCP Feedback Enhanced 默认使用端口 **8765**,您也可以自定义端口: + +![设置端口](../images/ssh-remote-port-setting.png) + +### 步骤二:等待 MCP 调用 + +**重要**:不要手动启动 Web UI,而是要等待 AI 模型调用 MCP 工具时自动启动。 + +当 AI 模型调用 `interactive_feedback` 工具时,系统会自动启动 Web UI。 + +### 步骤三:查看端口并连接 + +如果浏览器没有自动启动,您需要手动连接到 Web UI: + +#### 方法一:查看端口转发 +查看您的 SSH Remote 环境的端口转发设置,找到对应的本地端口: + +![连接到 URL](../images/ssh-remote-connect-url.png) + +#### 方法二:使用 Debug 模式查看 +在 IDE 中开启 Debug 模式,选择「输出」→「MCP Log」,可以看到 Web UI 的 URL: + +![Debug 模式查看端口](../images/ssh-remote-debug-port.png) + +### 步骤四:在本地浏览器打开 + +1. 复制 URL(通常是 `http://localhost:8765` 或其他端口) +2. 在本地浏览器中粘贴并打开 +3. 开始使用 Web UI 进行反馈 + +## 端口转发设置 + +### VS Code Remote SSH +1. 在 VS Code 中按 `Ctrl+Shift+P` +2. 输入 "Forward a Port" +3. 输入端口号(默认 8765) +4. 在本地浏览器中访问 `http://localhost:8765` + +### Cursor SSH Remote +1. 查看 Cursor 的端口转发设置 +2. 手动添加端口转发规则(端口 8765) +3. 在本地浏览器中访问转发的端口 + +## 重要提醒 + +### ⚠️ 不要手动启动 +**请勿**手动执行 `uvx mcp-feedback-enhanced test --web` 等指令,这样无法与 MCP 系统整合。 + +### ✅ 正确流程 +1. 等待 AI 模型调用 MCP 工具 +2. 系统自动启动 Web UI +3. 查看端口转发或 Debug 日志 +4. 在本地浏览器中打开对应 URL + +## 常见问题 + +### Q: 为什么在 SSH Remote 环境中无法自动打开浏览器? +A: SSH Remote 环境是无头环境(headless),没有图形界面,因此无法直接启动浏览器。需要通过端口转发在本地浏览器中访问。 + +### Q: 如何确认 Web UI 是否正常启动? +A: 查看 IDE 的 Debug 输出或 MCP Log,如果看到 "Web UI 已启动" 的信息,表示启动成功。 + +### Q: 端口被占用怎么办? +A: 在 MCP 设置中修改端口号,或者等待系统自动选择其他可用端口。 + +### Q: 找不到端口转发设置怎么办? +A: 查看您的 SSH Remote 工具文档,或使用 Debug 模式查看 MCP Log 中的 URL。 + +### Q: 为什么没有接收到 MCP 新的反馈? +A: 可能是 WebSocket 连接有问题。**解决方法**:直接重新刷新浏览器页面,这会重新建立 WebSocket 连接。 + +### Q: 为什么没有调用出 MCP? +A: 请确认 MCP 工具状态为绿灯(表示正常运作)。**解决方法**: +- 检查 IDE 中的 MCP 工具状态指示灯 +- 如果不是绿灯,尝试反复开关 MCP 工具 +- 等待几秒钟让系统重新连接 + +### Q: 为什么 Augment 无法启动 MCP? +A: 有时候可能会有错误导致 MCP 工具没有显示绿灯状态。**解决方法**: +- 完全关闭并重新启动 VS Code 或 Cursor +- 重新打开项目 +- 等待 MCP 工具重新加载并显示绿灯 + +## v2.3.0 改进 + +本版本针对 SSH Remote 环境的改进: +- ✅ 自动检测 SSH Remote 环境 +- ✅ 在无法启动浏览器时提供清晰的指引 +- ✅ 显示正确的访问 URL +- ✅ 改善错误提示和解决建议 + +## 相关资源 + +- [主要文档](../../README.zh-CN.md) diff --git a/docs/zh-TW/images/ssh-remote-connect-url.png b/docs/zh-TW/images/ssh-remote-connect-url.png new file mode 100644 index 0000000000000000000000000000000000000000..7c67e886bdbdf7b7b2d914a9a82509bc21508fad GIT binary patch literal 120104 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%0AE4Rju1zc51SpXOku^r5brDI^ zLhC1fs@>KC1yMCVD#B+T`OQBzLud0&mY7wHu6K%5*z-Vo_jV-A z4^M#b($a!M^#8jcGg(XCCLf%ME9^ZGVJS8v?x?g6aOGI!z7YAzh|m{CAW@lJS^pU1 zxN*lM=Kw)o|C!oKs|L!89f%B##P`@7;HxvTTRX*XwEajI)2kWb}YksN~ zGp8%Z#<%M~pwZhTSSf->+2?yR5J2Pn-v*jbKC&LKidgS^^z5LN|I@I^2V^M2fGeh~ zh;t;k541-eY|tAm1c){LKtEVsrCqzG7R+I->X(!BaslC%3~hNsi^I(8!%lDiF28Z& z|D4}Y`493Nl@lyeKke#2f9<^UUwB#stN-e02?p3orl*UodCLoM3S!@_)F5m0&z;ok z-0bfGw*);hhCK-B_xUV%u{GNkFMqJ9^j7W`N33>(dVnP>5GHPWu5SP0<_4|2S#0&X zuXuhhkg8|@hA2-?$P|4akFHV0r;WP1#)+JHfSqyC!^bd(-1@6y{R8bw2lcGTrNVS8 z&ETy?gNU`n&~-J1S|UhC#4BE#Wc?9$uQIX*c3l+Ed5@J)3U;ZI@ zE({XrYU}#Y7i*us2%tdp07Av=G^w`XtOG`;?q!l=8hqBvO}O|!DRfhIg`wA732hIQZ%@bJU5b*1fLiy#WpK!<+PJE4Xg+O%)p;+y-f zIX7=4#T7QF?xUvY*#M84KX+72Z8TkZ@gHLw6st#s=a$Oc*(3qGX$$K$JFR)HJF?<9 zZM|GqH#q4GhvF1KbMCaDyK${l?uqc}}`->6<~y zstRChKzaD{rUoEG;}17hNuQoEO?!pBLSDRIsqjVrjK@zhpiFI24iFP=QOZVHi{{cj z!rpk$m9}aft?;nm(7KVXrSg&Pum5idox?37m`9`ZuRmwMM?|E6-8kZVgR>=$ahogE z2BtiMvOw&Aszew)#G(>sbN7P+2_8Zge_>f_Tn1*($KPJ1J$8Bdpw=H=>39C_C9Y>0 z&ga2HH;B7OjxhevQNML3kET#;zyf3e)5y{kGrk%5NH8o^^djd98uiGg9q@ z*+=80*K%d0*^ixja^(2sBS&d&9yxaI0`mB|0p2?IK&jt;`lI zObWAUl%uC2@U-xw+&}69;WrP|LqY23nH77^N`&cP(enZ4#nMPzOu4Ls-AEAVsLkS?XI>@ADrE0L;Ur?wbxGrCl$oi<6O%f6>OyB1M9_3f09LGv^L zR^DugJKeU2?AWkq+ceiD=31E-?wmQ%>;4L-(=>U_uS;_^{K)a=T;aFQ56BZksxKPm zHm96Y0}a&9IRTc?` z6)_|!^zL^z5VUQ{Iqb8=>Q>G-v)EzvB>rQ{d2ixwU(n*HZNskj5;PY zIwB%S1*#iajGy-*l|jo(6%NEJb^EAh0*|b`Ik(`l_tM?B?zRUck9)slhJl-$1I|u* z8?zk0d{u_#bbV4NsazgCS9}1|umfmE(=)6OobT;}3+Lv{2R0ArdYV6e-)UEb9%P3M z=r|6&yF?74DgqAKFiZLh}upW3c7s;Q-G3#fpITtGww42S|syGpxAQ##TVrAfUM36Q{* z62t_Mdnp3aOX!FaBO)LzA<|SpN+8h)fdm2;0*01LNhqPb$G7<|UF-YzonL32nc1^v z&N_4UJhS(H7DAJ~VUYt)2G}7cN*e^FpoPP2A6zMxM!Fj`JNY~*4@su+5`XKGG6=c` zB+YJEXpDv1X5d&^Cs(Jfu*d>m?~bdvmZ$26rgnR_PdnHWh;HqOyS=m_kYnk%9=uP; zn;ADF0Z@z1c6A7j8;p?}Z1SDXRni+R4I_5_@X2}eqmjuVB;QR>J$BR#rLj0R!31qp zE&QyasY=hUkW`zEarS`+e!l!AvR1Q} zS__<|b-2#^%c?fSJlgqdoiNgtQ97I)8}fXypH)}Xq3iFXz_PdZ*U@b`!o~OCFt@Rz zMP1Zw8&KS$0D~zVS>p0?O?)O@mkF-sS4ywR?3g7+Y8$!jvPDPv7&mUnY0BI;<@V+= ztAXRVUbowM=BG(n$@=h0=D^BmBBQ$zX_>I~kexpoDHv$jRT&D`4ia5DrbjTYt_z_! zdt>txto&CEz;78dPV41BjvKGK*4ew8iwZ^Hr%he%Q)8`!h4bvxnL47!$lDQv#+}CX zm$o~&(Iusn=TuJ2op%mTBIgqou_S*OLxs76Mqr8Yma3h(YST+2ZlfF5l|gCcIE}}c z@%Iy~?w5>;%|I-AF0r26fJy`Y4q zPHQ{Z)e6Fe5uLZSD7Fh{eJp*J;=~+E0mCz--5yA)<0SO(ujAX7gl4A3S741zaMrv~ zflE$CaQ>Gqd_0u8gO&=aZD;n{oc^%eZ}C{kXJoZgn`3>k4O-apHdw+T^BPvMH9iyb z=*uexx>+TXy@>Uzjh&sE@vN%>=gV5js^Fgs-&!gYY!7l}eOh6ayO@d4A<^K3xTD=g zGiPT7U@mu5MP#5kRs+UMT3ZUu; zIv29u@$$@86&T~I6m9Q;rQ4SO0nWRxDyCb{)p5(nKBcatGoEVQsZX9P7d=Du58W|A zLlK0f5`7)v;_6iASTv5fnTj~Lc#@GIAg)zSJPF^f$0W0)a&j||G-t6(>lf>bWopDo z9qzb3G~1nYUz@>mqWr_;mz9;Bz5|LLv0qpp13R5Jt1t>pMFVx!YKzS>l*n1c8+T@t zhX$*nIU)SpFG1ng%7ir_Ud_kt=-oxDpdpHXxt}U-+Fm`1*dHicDuOTN3+NOb9@M~J zHXn+SX9U|Ge?I0i6#kqotpDbnnqO5bCRNIxV=3Xy%5T&(S+d@ zZ}wN`r&w@u_&c1l9^FvdzLEfVoeLGjgaVYZuyH>&R-g1iuU6{s=pb9W#-kHvvh;wtI<5@LTXacpI-sC`_PY>>oT){aSD>y2*$=)T4>$Xg53uL$Mf1_s2X zK9Y!X?+j=z#Ei$;U^+5TD;QC69nZfP*XF;}Yhs()gLd~PtscPNo=;+GE2&PzL@b`p zliCiF!&Gnrh5VF-Wwf9HI;DJ!tCi=)WK{!3D=h?Q#l`oASjK1T-?M`eO(xFNcL65X zC#O7(s5Xka9Qx@nv=Q|-dBd2%tMNuf&pxDVt`AYgp25Ww_xV$W~ zj1sY38_l*bY@7S^5dy2pBs<0iUzOuEJHsgE`fLt=xUU=tA$Jg_}9+pN}aV{ zVDkI;Ygt_@&14Hn3Ydn~NXy2FrUbXPg~DP(lS&=le{|NjSe{AM()Mb&hG;dXTyWW( zh?w~(AuxULgbq0HjyjAdOLC;@c89jkBmk#y%QhGvI*3*uzjJqm{<_bXVZop z4pFR0tPin@wO*Q@>u!##$M`Q4=mS*I7(;_lws+%Vm&y#;oS<#Kgl#DVJPF@yKM};U zXgc{Zy22~8lB)!F;_Z(YfnR(B-{An+o#E?tMYnx=3zKu?3zJSF`UcAlr<6A-BI^DG zDIndcvH zlRNNuyR>mUC3hmuu44;*BOX|3*wkGXDkW4ww?|6A*bt5ou!9 z4A~B^5L54XhlzeI$$YNmYWP*KEaIB(``l_!4G2u?AyqC3-WIaiikIiPSj8J_{RykD zWj9XjGaxoMQaf~S6+h1ygJ*v!ZT09%gag*-C&2mt5TmS2V6cNjxWEji`UbZx%ZI(; z@0FCiU3QVR+G9j@OdATBbeh!^o_DBrY!0k;wCKH9{O;l4j-C2OHqVO^*MxZED;DVZ zM9IO{zQ;Q%hOuJ0eOC^ZjD>m>qotcB*k7~di)Phc3#t^`NT0~_`QRvCwy%NPd@1XQ zDNOSX6EweM54vzn#Ef7jzPvHeBs%UWY9X(6cMb$01Bal~47CjG`;lR;TSYvCP*KsM zt=t&-_2ZnVc-sIZhZe>(J{JhVXTN8byi)~YP5mstwqsbG#BRAQsayHQW7$LprOL3y zhey3?qgQ!nDlP|r?^Q2nyt`+p{dFj>T=ZCjyAX@3LeHCnvof)=;Oi|2Qs6h*$y-1+ zeO4x~C(JdB3kSHE1+(nt+WvCWmBpvI3d8rcUb6~kpyC=bqxWaxa23qTot-NU)~yWpKMl1 zKl@Ru&Ne+z9zliISl%&qS%4Cqq7fA9d;ElfEVzLliE-+U``z@n*xm6V>HZ8B zRi6aK#55f^$M#Eg+jAg^)3;UVkyTr@tksf`zDNxxL&962P8FuN=FdjIF=9B?P`6Nx zIMc_X$)?kuA>H(+CtYfd_i@W*yvh_K33N2RYH1&;Rd?sC;D=Tq>+lyHa?+{x@dr;- zq^plSMQROSATi88`*zzdWwDXi{;qN(;Ty44Pu&hJl&P=jQ!_JdDT^!FR$0xwr}}qp zhf=UjG@|w~dU!yk4Vhe|AI>&7Tj_Utj}ft6ko<)WU8Lo+SOaf6ix;tyG_3%8rAlCF zh6;{gi8dZgauw}N%rV!B-<=FA4TQv3)qxCQ(&Ks%-^u7>3bS1#*pyIM6dLiGoK$+_ zHg5uM-89(g5B#z*Z!3MS)%U6=sjMGd3j0_qOviDBD`sS`gl+bo%Bt8sr6uf2+8xbB z*Hlycv!#g6Qxc*xnnpJ}kPXqc?MO_zAL!~zTEvujlc*B)ca(f*$sg(N8JqSC?bTwW z+Nm&4xxO^rD_cv!p@Z%7)CLP)z%)m28{1n59c9O*15N_4ewm<$EkFn>@Z}$m3%;)$ z`|;#Cb0v+iH;pqdZ#~i&$w%iqp%?loCqx1Wj*DfXHBQK}Y|)G}QF)ak26!(N-sar! zm_reoJQXr0yyAWLv8xyf{trcXFDUOfIo!PVUU%)=$N>JGHxmJ~DGC!$pM z;atku-=^w&m)~xm*p&xhAe4!F8)K6n(Az?%e<=T^Xg5C~^vI8c{6B>LLfZIAuS*Z_ z0l^OYj#eW{f{>l3eG@RNvN6ai#J^`5EyPg(-hzo743 z{YTo-Ux2`fSEtVY7uET171=a^BlO2vI>aYaDd3fL^vCBwr*C=x|K8<>(bitvF-fJY TS6<=!fXmX<)}+?R>(74x(*F8m literal 0 HcmV?d00001 diff --git a/docs/zh-TW/images/ssh-remote-port-setting.png b/docs/zh-TW/images/ssh-remote-port-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..80a64bc28451fd063cd8a01f587e5a99cdddbb04 GIT binary patch literal 28552 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 literal 0 HcmV?d00001 diff --git a/docs/zh-TW/ssh-remote/browser-launch-issues.md b/docs/zh-TW/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..d386007 --- /dev/null +++ b/docs/zh-TW/ssh-remote/browser-launch-issues.md @@ -0,0 +1,103 @@ +# SSH Remote 環境瀏覽器啟動問題解決方案 + +## 問題描述 + +在 SSH Remote 環境(如 Cursor SSH Remote、VS Code Remote SSH 、WSL 等)中使用 MCP Feedback Enhanced 時,可能會遇到以下問題: + +- 🚫 瀏覽器無法自動啟動 +- ❌ 顯示「無法啟動瀏覽器」錯誤 +- 🔗 Web UI 無法在本地瀏覽器中開啟 + +## 原因分析 + +SSH Remote 環境的限制: +1. **顯示環境隔離**: 遠端伺服器沒有圖形界面環境 +2. **網路隔離**: 遠端端口無法直接在本地訪問 +3. **瀏覽器不存在**: 遠端環境通常沒有安裝瀏覽器 + +## 解決方案 + +### 步驟一:設定端口(可選) + +MCP Feedback Enhanced 預設使用端口 **8765**,您也可以自定義端口: + +![設定端口](../images/ssh-remote-port-setting.png) + +### 步驟二:等待 MCP 呼叫 + +**重要**:不要手動啟動 Web UI,而是要等待 AI 模型呼叫 MCP 工具時自動啟動。 + +當 AI 模型呼叫 `interactive_feedback` 工具時,系統會自動啟動 Web UI。 + +### 步驟三:查看端口並連接 + +如果瀏覽器沒有自動啟動,您需要手動連接到 Web UI: + +#### 方法一:查看端口轉發 +查看您的 SSH Remote 環境的端口轉發設定,找到對應的本地端口: + +![連接到 URL](../images/ssh-remote-connect-url.png) + +#### 方法二:使用 Debug 模式查看 +在 IDE 中開啟 Debug 模式,選擇「輸出」→「MCP Log」,可以看到 Web UI 的 URL: + +![Debug 模式查看端口](../images/ssh-remote-debug-port.png) + +### 步驟四:在本地瀏覽器開啟 + +1. 複製 URL(通常是 `http://localhost:8765` 或其他端口) +2. 在本地瀏覽器中貼上並開啟 +3. 開始使用 Web UI 進行回饋 + +## 端口轉發設定 + +### VS Code Remote SSH +1. 在 VS Code 中按 `Ctrl+Shift+P` +2. 輸入 "Forward a Port" +3. 輸入端口號(預設 8765) +4. 在本地瀏覽器中訪問 `http://localhost:8765` + +### Cursor SSH Remote +1. 查看 Cursor 的端口轉發設定 +2. 手動添加端口轉發規則(端口 8765) +3. 在本地瀏覽器中訪問轉發的端口 + +## 重要提醒 + +### ⚠️ 不要手動啟動 +**請勿**手動執行 `uvx mcp-feedback-enhanced test --web` 等指令,這樣無法與 MCP 系統整合。 + +### ✅ 正確流程 +1. 等待 AI 模型呼叫 MCP 工具 +2. 系統自動啟動 Web UI +3. 查看端口轉發或 Debug 日誌 +4. 在本地瀏覽器中開啟對應 URL + +## 常見問題 + +### Q: 為什麼在 SSH Remote 環境中無法自動開啟瀏覽器? +A: SSH Remote 環境是無頭環境(headless),沒有圖形界面,因此無法直接啟動瀏覽器。需要通過端口轉發在本地瀏覽器中訪問。 + +### Q: 如何確認 Web UI 是否正常啟動? +A: 查看 IDE 的 Debug 輸出或 MCP Log,如果看到 "Web UI 已啟動" 的訊息,表示啟動成功。 + +### Q: 端口被占用怎麼辦? +A: 在 MCP 設定中修改端口號,或者等待系統自動選擇其他可用端口。 + +### Q: 找不到端口轉發設定怎麼辦? +A: 查看您的 SSH Remote 工具文檔,或使用 Debug 模式查看 MCP Log 中的 URL。 + +### Q: 為什麼沒有接收到 MCP 新的反饋? +A: 可能是 WebSocket 連接有問題。**解決方法**:直接重新整理瀏覽器頁面,這會重新建立 WebSocket 連接。 + +### Q: 為什麼沒有呼叫出 MCP? +A: 請確認 MCP 工具狀態為綠燈(表示正常運作)。**解決方法**: +- 檢查 IDE 中的 MCP 工具狀態指示燈 +- 如果不是綠燈,嘗試反覆開關 MCP 工具 +- 等待幾秒鐘讓系統重新連接 + +### Q: 為什麼 Augment 無法啟動 MCP? +A: 有時候可能會有錯誤導致 MCP 工具沒有顯示綠燈狀態。**解決方法**: +- 完全關閉並重新啟動 VS Code 或 Cursor +- 重新開啟專案 +- 等待 MCP 工具重新載入並顯示綠燈 diff --git a/issues/WSL環境預設使用WebUI修復.md b/issues/WSL環境預設使用WebUI修復.md new file mode 100644 index 0000000..53c215b --- /dev/null +++ b/issues/WSL環境預設使用WebUI修復.md @@ -0,0 +1,86 @@ +# WSL 環境預設使用 Web UI 修復 + +## 任務描述 +修復 WSL 環境中 MCP 服務器錯誤地偵測為可使用 GUI 的問題。WSL 環境應該預設使用 Web UI,因為大多數 WSL 安裝都是 Linux 環境,沒有桌面應用支援。 + +## 問題分析 +根據 MCP log 顯示: +``` +[SERVER] 偵測到 WSL 環境(通過 /proc/version) +[SERVER] WSL 環境不被視為遠端環境 +[SERVER] 成功載入 PySide6,可使用 GUI +[SERVER] GUI 可用: True +``` + +問題在於 `can_use_gui()` 函數沒有考慮 WSL 環境的特殊性: +- WSL 環境不被視為遠端環境(正確) +- 但 WSL 環境中即使 PySide6 可以載入,也應該預設使用 Web UI + +## 解決方案 +採用方案 1:在 `can_use_gui()` 函數中直接檢查 WSL 環境 + +### 修改內容 +1. **文件**:`src\mcp_feedback_enhanced\server.py` +2. **函數**:`can_use_gui()` (第 203-230 行) +3. **修改邏輯**: + - 保持現有的遠端環境檢查 + - 在遠端環境檢查後,添加 WSL 環境檢查 + - 如果是 WSL 環境,直接返回 `False` + - 保持其餘 PySide6 載入檢查邏輯不變 + +### 修改前後對比 +**修改前**: +```python +def can_use_gui() -> bool: + if is_remote_environment(): + return False + + try: + from PySide6.QtWidgets import QApplication + debug_log("成功載入 PySide6,可使用 GUI") + return True + # ... +``` + +**修改後**: +```python +def can_use_gui() -> bool: + if is_remote_environment(): + return False + + # WSL 環境預設使用 Web UI + if is_wsl_environment(): + debug_log("WSL 環境偵測到,預設使用 Web UI") + return False + + try: + from PySide6.QtWidgets import QApplication + debug_log("成功載入 PySide6,可使用 GUI") + return True + # ... +``` + +## 預期結果 +修改後,WSL 環境的 MCP log 應該顯示: +``` +[SERVER] 偵測到 WSL 環境(通過 /proc/version) +[SERVER] WSL 環境不被視為遠端環境 +[SERVER] WSL 環境偵測到,預設使用 Web UI +[SERVER] GUI 可用: False +[SERVER] 建議介面: Web UI +``` + +## 影響範圍 +- ✅ WSL 環境將預設使用 Web UI +- ✅ 不影響其他環境的邏輯 +- ✅ 保持向後兼容性 +- ✅ 用戶仍可通過 `FORCE_WEB` 環境變數控制介面選擇 + +## 測試建議 +1. 在 WSL 環境中測試 MCP 服務器啟動 +2. 驗證日誌顯示正確的環境偵測結果 +3. 確認使用 Web UI 而非 GUI +4. 測試 `FORCE_WEB` 環境變數仍然有效 + +## 完成時間 +2025-06-08 01:45:00 From b2640b51adcf08e489cd3681f54c3ee91bf23df1 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sun, 8 Jun 2025 02:58:33 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=93=9D=20=E7=B0=A1=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E7=99=BC=E5=B8=83=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 241 +++++++++++++-------------- RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md | 147 ++++++++++++++++ RELEASE_NOTES/v2.2.1/en.md | 28 ---- RELEASE_NOTES/v2.2.1/zh-CN.md | 28 ---- RELEASE_NOTES/v2.2.1/zh-TW.md | 28 ---- RELEASE_NOTES/v2.2.2/en.md | 30 ---- RELEASE_NOTES/v2.2.2/zh-CN.md | 30 ---- RELEASE_NOTES/v2.2.2/zh-TW.md | 30 ---- RELEASE_NOTES/v2.2.3/en.md | 42 ----- RELEASE_NOTES/v2.2.3/zh-CN.md | 42 ----- RELEASE_NOTES/v2.2.3/zh-TW.md | 42 ----- RELEASE_NOTES/v2.2.4/en.md | 23 --- RELEASE_NOTES/v2.2.4/zh-CN.md | 23 --- RELEASE_NOTES/v2.2.4/zh-TW.md | 23 --- RELEASE_NOTES/v2.2.5/en.md | 28 ---- RELEASE_NOTES/v2.2.5/zh-CN.md | 28 ---- RELEASE_NOTES/v2.2.5/zh-TW.md | 28 ---- 17 files changed, 263 insertions(+), 578 deletions(-) create mode 100644 RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md delete mode 100644 RELEASE_NOTES/v2.2.1/en.md delete mode 100644 RELEASE_NOTES/v2.2.1/zh-CN.md delete mode 100644 RELEASE_NOTES/v2.2.1/zh-TW.md delete mode 100644 RELEASE_NOTES/v2.2.2/en.md delete mode 100644 RELEASE_NOTES/v2.2.2/zh-CN.md delete mode 100644 RELEASE_NOTES/v2.2.2/zh-TW.md delete mode 100644 RELEASE_NOTES/v2.2.3/en.md delete mode 100644 RELEASE_NOTES/v2.2.3/zh-CN.md delete mode 100644 RELEASE_NOTES/v2.2.3/zh-TW.md delete mode 100644 RELEASE_NOTES/v2.2.4/en.md delete mode 100644 RELEASE_NOTES/v2.2.4/zh-CN.md delete mode 100644 RELEASE_NOTES/v2.2.4/zh-TW.md delete mode 100644 RELEASE_NOTES/v2.2.5/en.md delete mode 100644 RELEASE_NOTES/v2.2.5/zh-CN.md delete mode 100644 RELEASE_NOTES/v2.2.5/zh-TW.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a789f09..f4e587e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,9 +9,9 @@ on: default: 'patch' type: choice options: - - patch # 2.0.0 -> 2.0.1 (bug fixes) - - minor # 2.0.0 -> 2.1.0 (new features) - - major # 2.0.0 -> 3.0.0 (breaking changes) + - patch # 2.0.0 -> 2.0.1 (bug fixes, security patches, documentation updates) + - minor # 2.0.0 -> 2.1.0 (new features, enhancements, backward-compatible changes) + - major # 2.0.0 -> 3.0.0 (breaking changes, architecture refactoring, API changes) jobs: release: @@ -70,167 +70,156 @@ jobs: NEW_VERSION="${{ steps.bump_version.outputs.new }}" sed -i "s/__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" src/mcp_feedback_enhanced/__init__.py - - name: Check Release Notes - id: check_notes + - name: Extract Release Highlights + id: extract_highlights run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="RELEASE_NOTES/${NEW_VERSION}" - - if [ ! -d "$RELEASE_DIR" ]; then - echo "❌ Error: Release notes directory not found at $RELEASE_DIR" - echo "Please create release notes before publishing!" - echo "Required files:" - echo " - $RELEASE_DIR/en.md" - echo " - $RELEASE_DIR/zh-TW.md" - exit 1 + + # Extract highlights from English CHANGELOG + if [ -f "RELEASE_NOTES/CHANGELOG.en.md" ]; then + # Find the section for the new version and extract highlights + awk "/## \[${NEW_VERSION}\]/{flag=1; next} /## \[/{flag=0} flag && /### 🌟 Highlights/{getline; while(getline && !/^###/ && !/^##/) if(/^[^[:space:]]/ || /^- /) print}' RELEASE_NOTES/CHANGELOG.en.md > highlights.txt + + # If no highlights section found, extract from new features + if [ ! -s highlights.txt ]; then + awk "/## \[${NEW_VERSION}\]/{flag=1; next} /## \[/{flag=0} flag && /### ✨ New Features/{getline; while(getline && !/^###/ && !/^##/) if(/^- /) print}' RELEASE_NOTES/CHANGELOG.en.md | head -4 > highlights.txt + fi + + echo "✅ Extracted highlights for $NEW_VERSION" + else + echo "⚠️ CHANGELOG.en.md not found, using default highlights" + echo "- 🚀 New features and improvements" > highlights.txt + echo "- 🐛 Bug fixes and optimizations" >> highlights.txt fi - - if [ ! -f "$RELEASE_DIR/en.md" ]; then - echo "❌ Error: English release notes not found at $RELEASE_DIR/en.md" - exit 1 - fi - - if [ ! -f "$RELEASE_DIR/zh-TW.md" ]; then - echo "❌ Error: Traditional Chinese release notes not found at $RELEASE_DIR/zh-TW.md" - exit 1 - fi - - if [ ! -f "$RELEASE_DIR/zh-CN.md" ]; then - echo "❌ Error: Simplified Chinese release notes not found at $RELEASE_DIR/zh-CN.md" - exit 1 - fi - - echo "✅ Release notes found for $NEW_VERSION" - echo "release_dir=$RELEASE_DIR" >> $GITHUB_OUTPUT - name: Generate Release Body id: release_body run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="${{ steps.check_notes.outputs.release_dir }}" - - # Create multi-language release body - cat > release_body.md << 'EOF' - ## 🌐 Multi-Language Release Notes - + + # Get release title from English CHANGELOG + RELEASE_TITLE=$(awk "/## \[${NEW_VERSION}\]/{print; exit}" RELEASE_NOTES/CHANGELOG.en.md | sed 's/## \[.*\] - //') + if [ -z "$RELEASE_TITLE" ]; then + RELEASE_TITLE="Latest Release" + fi + + # Create release body with highlights and links + cat > release_body.md << EOF + # Release ${NEW_VERSION} - ${RELEASE_TITLE} + + ## 🌟 Key Highlights + EOF + + # Add highlights + if [ -s highlights.txt ]; then + cat highlights.txt >> release_body.md + else + echo "- 🚀 New features and improvements" >> release_body.md + echo "- 🐛 Bug fixes and optimizations" >> release_body.md + fi + + # Add multi-language links section + cat >> release_body.md << 'EOF' + + ## 🌐 Detailed Release Notes + ### 🇺🇸 English - EOF - - # Add English content - cat "$RELEASE_DIR/en.md" >> release_body.md - - # Add separator - cat >> release_body.md << 'EOF' - - --- - + 📖 **[View Complete English Release Notes](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.en.md)** + ### 🇹🇼 繁體中文 - EOF - - # Add Traditional Chinese content - cat "$RELEASE_DIR/zh-TW.md" >> release_body.md - - # Add separator - cat >> release_body.md << 'EOF' - - --- - + 📖 **[查看完整繁體中文發布說明](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.zh-TW.md)** + ### 🇨🇳 简体中文 - EOF - - # Add Simplified Chinese content - cat "$RELEASE_DIR/zh-CN.md" >> release_body.md - - # Add installation section - cat >> release_body.md << 'EOF' - + 📖 **[查看完整简体中文发布说明](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.zh-CN.md)** + --- - - ## 📦 Installation & Update - + + ## 📦 Quick Installation / 快速安裝 + ```bash - # Quick test latest version - uvx mcp-feedback-enhanced@latest test - - # Update to this specific version + # Latest version / 最新版本 + uvx mcp-feedback-enhanced@latest + + # This specific version / 此特定版本 EOF - - echo "uvx mcp-feedback-enhanced@$NEW_VERSION test" >> release_body.md - + + echo "uvx mcp-feedback-enhanced@${NEW_VERSION}" >> release_body.md + cat >> release_body.md << 'EOF' ``` - + ## 🔗 Links - **Documentation**: [README.md](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/README.md) - **Full Changelog**: [CHANGELOG](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/) - **Issues**: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) - + --- - **Release automatically generated from RELEASE_NOTES system** 🤖 + **Release automatically generated from CHANGELOG system** 🤖 EOF - + echo "Release body generated successfully" - - name: Sync CHANGELOG Files + - name: Verify CHANGELOG Files run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="${{ steps.check_notes.outputs.release_dir }}" - - # Function to add version to changelog - add_to_changelog() { - local lang_file="$1" - local changelog_file="$2" - local temp_file="changelog_temp.md" - - # Get the header and separator - head -n 5 "$changelog_file" > "$temp_file" - - # Add the new version content - cat "$lang_file" >> "$temp_file" - - # Add separator - echo "" >> "$temp_file" - echo "---" >> "$temp_file" - echo "" >> "$temp_file" - - # Add the rest of the changelog (skip header) - tail -n +7 "$changelog_file" >> "$temp_file" - - # Replace the original file - mv "$temp_file" "$changelog_file" - - echo "✅ Updated $changelog_file" - } - - # Check if CHANGELOG files exist + + # Check if CHANGELOG files exist and contain the new version + echo "🔍 Verifying CHANGELOG files contain version ${NEW_VERSION}..." + + MISSING_FILES="" + if [ -f "RELEASE_NOTES/CHANGELOG.en.md" ]; then - add_to_changelog "$RELEASE_DIR/en.md" "RELEASE_NOTES/CHANGELOG.en.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.en.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.en.md" + MISSING_FILES="${MISSING_FILES} en" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.en.md" + fi else - echo "⚠️ CHANGELOG.en.md not found, skipping" + echo "❌ CHANGELOG.en.md not found" + MISSING_FILES="${MISSING_FILES} en" fi - + if [ -f "RELEASE_NOTES/CHANGELOG.zh-TW.md" ]; then - add_to_changelog "$RELEASE_DIR/zh-TW.md" "RELEASE_NOTES/CHANGELOG.zh-TW.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.zh-TW.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.zh-TW.md" + MISSING_FILES="${MISSING_FILES} zh-TW" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.zh-TW.md" + fi else - echo "⚠️ CHANGELOG.zh-TW.md not found, skipping" + echo "❌ CHANGELOG.zh-TW.md not found" + MISSING_FILES="${MISSING_FILES} zh-TW" fi - + if [ -f "RELEASE_NOTES/CHANGELOG.zh-CN.md" ]; then - add_to_changelog "$RELEASE_DIR/zh-CN.md" "RELEASE_NOTES/CHANGELOG.zh-CN.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.zh-CN.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.zh-CN.md" + MISSING_FILES="${MISSING_FILES} zh-CN" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.zh-CN.md" + fi else - echo "⚠️ CHANGELOG.zh-CN.md not found, skipping" + echo "❌ CHANGELOG.zh-CN.md not found" + MISSING_FILES="${MISSING_FILES} zh-CN" + fi + + if [ -n "$MISSING_FILES" ]; then + echo "" + echo "📝 Note: Please ensure CHANGELOG files are updated with version ${NEW_VERSION}" + echo "Missing or incomplete files:${MISSING_FILES}" + echo "The release will continue, but manual CHANGELOG updates may be needed." + else + echo "✅ All CHANGELOG files verified successfully" fi - - echo "📝 CHANGELOG files synchronized" - - name: Commit version bump and changelog updates + - name: Commit version bump run: | git add . git commit -m "🔖 Release v${{ steps.bump_version.outputs.new }} - Updated version to ${{ steps.bump_version.outputs.new }} - - Synchronized CHANGELOG files with release notes - - Auto-generated from RELEASE_NOTES system" + - Auto-generated release from simplified workflow" git tag "v${{ steps.bump_version.outputs.new }}" - name: Build package @@ -268,9 +257,11 @@ jobs: echo "" echo "📦 Published to PyPI: https://pypi.org/project/mcp-feedback-enhanced/" echo "🚀 GitHub Release: https://github.com/Minidoracat/mcp-feedback-enhanced/releases/tag/v${{ steps.bump_version.outputs.new }}" - echo "📝 CHANGELOG files have been automatically updated" + echo "📝 Release notes generated from CHANGELOG files" echo "" echo "✅ Next steps:" echo " - Check the release on GitHub" echo " - Verify the package on PyPI" - echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }} test" \ No newline at end of file + echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }}" + echo "" + echo "📋 Note: Make sure CHANGELOG files are updated for future releases" \ No newline at end of file diff --git a/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md new file mode 100644 index 0000000..bece0c7 --- /dev/null +++ b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md @@ -0,0 +1,147 @@ +# 簡化發布流程 / Simplified Release Workflow + +## 🎯 概述 / Overview + +此專案已採用簡化的發布流程,不再需要建立版本化目錄(如 `v2.3.0/`),而是直接更新 CHANGELOG 文件。 + +This project now uses a simplified release workflow that no longer requires creating versioned directories (like `v2.3.0/`), but instead directly updates CHANGELOG files. + +## 📋 新的發布流程 / New Release Process + +### 1. 更新 CHANGELOG 文件 / Update CHANGELOG Files + +在發布前,請手動更新以下三個文件: +Before releasing, manually update these three files: + +- `RELEASE_NOTES/CHANGELOG.en.md` +- `RELEASE_NOTES/CHANGELOG.zh-TW.md` +- `RELEASE_NOTES/CHANGELOG.zh-CN.md` + +### 2. CHANGELOG 格式要求 / CHANGELOG Format Requirements + +每個新版本應該按照以下格式添加到 CHANGELOG 文件的頂部: +Each new version should be added to the top of CHANGELOG files in this format: + +```markdown +## [v2.3.0] - 版本標題 / Version Title + +### 🌟 亮點 / Highlights +本次發佈的主要特色... + +### ✨ 新功能 / New Features +- 🆕 **功能名稱**: 功能描述 + +### 🐛 錯誤修復 / Bug Fixes +- 🔧 **問題修復**: 修復描述 + +### 🚀 改進功能 / Improvements +- ⚡ **效能優化**: 優化描述 + +--- +``` + +### 3. 執行發布 / Execute Release + +1. 確保所有 CHANGELOG 文件都已更新 + Ensure all CHANGELOG files are updated + +2. 前往 GitHub Actions 頁面 + Go to GitHub Actions page + +3. 執行 "Auto Release to PyPI" workflow + Run "Auto Release to PyPI" workflow + +4. 選擇版本類型(patch/minor/major) + Select version type (patch/minor/major) + +### 📊 版本類型說明 / Version Type Explanation + +選擇適當的版本類型非常重要,請根據變更內容選擇: +Choosing the appropriate version type is important, select based on the changes: + +#### 🔧 Patch (修補版本) +- **用途 / Usage**: 錯誤修復、小幅改進、安全修補 +- **範例 / Example**: `2.3.0 → 2.3.1` +- **適用情況 / When to use**: + - 🐛 修復 bug / Bug fixes + - 🔒 安全性修補 / Security patches + - 📝 文檔更新 / Documentation updates + - 🎨 小幅 UI 調整 / Minor UI tweaks + +#### ✨ Minor (次要版本) +- **用途 / Usage**: 新功能、功能增強、向後相容的變更 +- **範例 / Example**: `2.3.0 → 2.4.0` +- **適用情況 / When to use**: + - 🆕 新增功能 / New features + - 🚀 功能增強 / Feature enhancements + - 🎯 效能改進 / Performance improvements + - 🌐 新的語言支援 / New language support + +#### 🚨 Major (主要版本) +- **用途 / Usage**: 重大變更、不向後相容的修改、架構重構 +- **範例 / Example**: `2.3.0 → 3.0.0` +- **適用情況 / When to use**: + - 💥 破壞性變更 / Breaking changes + - 🏗️ 架構重構 / Architecture refactoring + - 🔄 API 變更 / API changes + - 📦 依賴項重大更新 / Major dependency updates + +#### 🤔 如何選擇 / How to Choose + +**問自己這些問題 / Ask yourself these questions**: + +1. **會破壞現有功能嗎?** / **Will it break existing functionality?** + - 是 / Yes → Major + - 否 / No → 繼續下一個問題 / Continue to next question + +2. **是否新增了功能?** / **Does it add new functionality?** + - 是 / Yes → Minor + - 否 / No → 繼續下一個問題 / Continue to next question + +3. **只是修復或小幅改進?** / **Just fixes or minor improvements?** + - 是 / Yes → Patch + +## 🔄 自動化流程 / Automated Process + +GitHub workflow 將自動: +The GitHub workflow will automatically: + +1. ✅ 版本號碼升級 / Version bump +2. ✅ 從 CHANGELOG 提取亮點 / Extract highlights from CHANGELOG +3. ✅ 生成多語系 GitHub Release / Generate multi-language GitHub Release +4. ✅ 發布到 PyPI / Publish to PyPI +5. ✅ 建立 Git 標籤 / Create Git tags + +## 📦 GitHub Release 格式 / GitHub Release Format + +自動生成的 Release 將包含: +Auto-generated releases will include: + +- 🌟 版本亮點 / Version highlights +- 🌐 多語系 CHANGELOG 連結 / Multi-language CHANGELOG links +- 📦 安裝指令 / Installation commands +- 🔗 相關連結 / Related links + +## ⚠️ 注意事項 / Important Notes + +1. **不再需要版本目錄**:舊的 `RELEASE_NOTES/v2.x.x/` 目錄結構已棄用 + **No more version directories**: Old `RELEASE_NOTES/v2.x.x/` directory structure is deprecated + +2. **手動更新 CHANGELOG**:發布前必須手動更新 CHANGELOG 文件 + **Manual CHANGELOG updates**: CHANGELOG files must be manually updated before release + +3. **格式一致性**:請保持 CHANGELOG 格式的一致性以確保自動提取正常運作 + **Format consistency**: Maintain CHANGELOG format consistency for proper auto-extraction + +## 🗂️ 舊版本目錄清理 / Old Version Directory Cleanup + +現有的版本目錄(`v2.2.1` 到 `v2.2.5`)可以選擇性保留作為歷史記錄,或者清理以簡化專案結構。 + +Existing version directories (`v2.2.1` to `v2.2.5`) can optionally be kept for historical records or cleaned up to simplify project structure. + +## 🚀 優點 / Benefits + +- ✅ 減少維護負擔 / Reduced maintenance burden +- ✅ 單一真實來源 / Single source of truth +- ✅ 簡化的專案結構 / Simplified project structure +- ✅ 自動化的 Release 生成 / Automated release generation diff --git a/RELEASE_NOTES/v2.2.1/en.md b/RELEASE_NOTES/v2.2.1/en.md deleted file mode 100644 index bbaa681..0000000 --- a/RELEASE_NOTES/v2.2.1/en.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - Window Optimization & Unified Settings Interface - -## 🌟 Highlights -This release primarily addresses GUI window size constraints, implements smart window state saving mechanisms, and optimizes the unified settings interface. - -## 🚀 Improvements -- 🖥️ **Window Size Constraint Removal**: Removed GUI main window minimum size limit from 1000×800 to 400×300, allowing users to freely adjust window size for different use cases -- 💾 **Real-time Window State Saving**: Implemented real-time saving mechanism for window size and position changes, with debounce delay to avoid excessive I/O operations -- ⚙️ **Unified Settings Interface Optimization**: Improved GUI settings page configuration saving logic to avoid setting conflicts, ensuring correct window positioning and size settings -- 🎯 **Smart Window Size Saving**: In "Always center display" mode, correctly saves window size (but not position); in "Smart positioning" mode, saves complete window state - -## 🐛 Bug Fixes -- 🔧 **Window Size Constraint**: Fixed GUI window unable to resize to small dimensions issue (fixes #10 part one) -- 🛡️ **Setting Conflicts**: Fixed potential configuration conflicts during settings save operations - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Issues Addressed: #10 (partially completed) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.1/zh-CN.md b/RELEASE_NOTES/v2.2.1/zh-CN.md deleted file mode 100644 index 793c239..0000000 --- a/RELEASE_NOTES/v2.2.1/zh-CN.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - 窗口优化与统一设置接口 - -## 🌟 亮点 -本版本主要解决了 GUI 窗口大小限制问题,实现了窗口状态的智能保存机制,并优化了设置接口的统一性。 - -## 🚀 改进功能 -- 🖥️ **窗口大小限制解除**: 解除 GUI 主窗口最小大小限制,从 1000×800 降至 400×300,让用户可以自由调整窗口大小以符合不同使用场景 -- 💾 **窗口状态实时保存**: 实现窗口大小与位置的即时保存机制,支持防抖延迟避免过度频繁的 I/O 操作 -- ⚙️ **统一设置接口优化**: 改进 GUI 设置版面的配置保存逻辑,避免设置冲突,确保窗口定位与大小设置的正确性 -- 🎯 **智能窗口大小保存**: 「总是在主屏幕中心显示」模式下正确保存窗口大小(但不保存位置),「智能定位」模式下保存完整的窗口状态 - -## 🐛 问题修复 -- 🔧 **窗口大小限制**: 解决 GUI 窗口无法调整至小尺寸的问题 (fixes #10 第一部分) -- 🛡️ **设置冲突**: 修复设置保存时可能出现的配置冲突问题 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解决问题: #10 (部分完成) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.1/zh-TW.md b/RELEASE_NOTES/v2.2.1/zh-TW.md deleted file mode 100644 index aca5a3f..0000000 --- a/RELEASE_NOTES/v2.2.1/zh-TW.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - 視窗優化與統一設定接口 - -## 🌟 亮點 -本版本主要解決了 GUI 視窗大小限制問題,實現了視窗狀態的智能保存機制,並優化了設定接口的統一性。 - -## 🚀 改進功能 -- 🖥️ **視窗大小限制解除**: 解除 GUI 主視窗最小大小限制,從 1000×800 降至 400×300,讓用戶可以自由調整視窗大小以符合不同使用場景 -- 💾 **視窗狀態實時保存**: 實現視窗大小與位置的即時保存機制,支援防抖延遲避免過度頻繁的 I/O 操作 -- ⚙️ **統一設定接口優化**: 改進 GUI 設定版面的配置保存邏輯,避免設定衝突,確保視窗定位與大小設定的正確性 -- 🎯 **智能視窗大小保存**: 「總是在主螢幕中心顯示」模式下正確保存視窗大小(但不保存位置),「智能定位」模式下保存完整的視窗狀態 - -## 🐛 問題修復 -- 🔧 **視窗大小限制**: 解決 GUI 視窗無法調整至小尺寸的問題 (fixes #10 第一部分) -- 🛡️ **設定衝突**: 修復設定保存時可能出現的配置衝突問題 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解決問題: #10 (部分完成) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/en.md b/RELEASE_NOTES/v2.2.2/en.md deleted file mode 100644 index 69a134d..0000000 --- a/RELEASE_NOTES/v2.2.2/en.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - Timeout Auto-cleanup Fix - -## 🌟 Highlights -This version fixes a critical resource management issue where GUI/Web UI interfaces were not properly closed when MCP sessions ended due to timeout, causing the interfaces to remain open and unresponsive. - -## 🐛 Bug Fixes -- 🔄 **Timeout Auto-cleanup**: Fixed GUI/Web UI not automatically closing after MCP session timeout (default 600 seconds) -- 🛡️ **Resource Management Optimization**: Improved timeout handling mechanism to ensure proper cleanup and closure of all UI resources on timeout -- ⚡ **Enhanced Timeout Detection**: Strengthened timeout detection logic to correctly handle timeout events in various scenarios -- 🔧 **Interface Response Improvement**: Enhanced Web UI frontend handling of session timeout events - -## 🚀 Technical Improvements -- 📦 **Web Session Management**: Refactored WebFeedbackSession timeout handling logic -- 🎯 **QTimer Integration**: Introduced precise QTimer timeout control mechanism in GUI -- 🌐 **Frontend Communication Optimization**: Improved timeout message communication between Web UI frontend and backend -- 🧹 **Resource Cleanup Mechanism**: Added _cleanup_resources_on_timeout method to ensure thorough cleanup - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Fixed Issue: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/zh-CN.md b/RELEASE_NOTES/v2.2.2/zh-CN.md deleted file mode 100644 index 7e8437f..0000000 --- a/RELEASE_NOTES/v2.2.2/zh-CN.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - 超时自动清理修复 - -## 🌟 亮点 -本版本修复了一个重要的资源管理问题:当 MCP session 因超时结束时,GUI/Web UI 界面没有正确关闭,导致界面持续显示而无法正常关闭。 - -## 🐛 问题修复 -- 🔄 **超时自动清理**: 修复 GUI/Web UI 在 MCP session timeout (默认 600 秒) 后没有自动关闭的问题 -- 🛡️ **资源管理优化**: 改进超时处理机制,确保在超时时正确清理和关闭所有 UI 资源 -- ⚡ **超时检测增强**: 加强超时检测逻辑,确保在各种情况下都能正确处理超时事件 -- 🔧 **界面响应改进**: 改善 Web UI 前端对 session timeout 事件的处理响应 - -## 🚀 技术改进 -- 📦 **Web Session 管理**: 重构 WebFeedbackSession 的超时处理逻辑 -- 🎯 **QTimer 整合**: 在 GUI 中引入精确的 QTimer 超时控制机制 -- 🌐 **前端通信优化**: 改进 Web UI 前端与后端的超时消息传递 -- 🧹 **资源清理机制**: 新增 _cleanup_resources_on_timeout 方法确保彻底清理 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解决问题: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/zh-TW.md b/RELEASE_NOTES/v2.2.2/zh-TW.md deleted file mode 100644 index c83280b..0000000 --- a/RELEASE_NOTES/v2.2.2/zh-TW.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - 超時自動清理修復 - -## 🌟 亮點 -本版本修復了一個重要的資源管理問題:當 MCP session 因超時結束時,GUI/Web UI 介面沒有正確關閉,導致介面持續顯示而無法正常關閉。 - -## 🐛 問題修復 -- 🔄 **超時自動清理**: 修復 GUI/Web UI 在 MCP session timeout (預設 600 秒) 後沒有自動關閉的問題 -- 🛡️ **資源管理優化**: 改進超時處理機制,確保在超時時正確清理和關閉所有 UI 資源 -- ⚡ **超時檢測增強**: 加強超時檢測邏輯,確保在各種情況下都能正確處理超時事件 -- 🔧 **介面回應改進**: 改善 Web UI 前端對 session timeout 事件的處理回應 - -## 🚀 技術改進 -- 📦 **Web Session 管理**: 重構 WebFeedbackSession 的超時處理邏輯 -- 🎯 **QTimer 整合**: 在 GUI 中引入精確的 QTimer 超時控制機制 -- 🌐 **前端通訊優化**: 改進 Web UI 前端與後端的超時訊息傳遞 -- 🧹 **資源清理機制**: 新增 _cleanup_resources_on_timeout 方法確保徹底清理 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解決問題: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.3/en.md b/RELEASE_NOTES/v2.2.3/en.md deleted file mode 100644 index daae09f..0000000 --- a/RELEASE_NOTES/v2.2.3/en.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - Timeout Control & Image Settings Enhancement - -## 🌟 Highlights -This version introduces user-controllable timeout settings and flexible image upload configuration options, while improving UV Cache management tools to enhance the overall user experience. - -## ✨ New Features -- ⏰ **User Timeout Control**: Added customizable timeout settings with flexible range from 30 seconds to 2 hours -- ⏱️ **Countdown Timer**: Real-time countdown timer display at the top of the interface for visual time reminders -- 🖼️ **Image Size Limits**: Added image upload size limit settings (unlimited/1MB/3MB/5MB) -- 🔧 **Base64 Compatibility Mode**: Added Base64 detail mode to improve image recognition compatibility with AI models -- 🧹 **UV Cache Management Tool**: Added `cleanup_cache.py` script to help manage and clean UV cache space - -## 🚀 Improvements -- 📚 **Documentation Structure Optimization**: Reorganized documentation directory structure, moved images to `docs/{language}/images/` paths -- 📖 **Cache Management Guide**: Added detailed UV Cache management guide with automated cleanup solutions -- 🎯 **Smart Compatibility Hints**: Automatically display Base64 compatibility mode suggestions when image upload fails -- 🔄 **Settings Sync Mechanism**: Improved image settings synchronization between different interface modes - -## 🐛 Bug Fixes -- 🛡️ **Timeout Handling Optimization**: Improved coordination between user-defined timeout and MCP system timeout -- 🖥️ **Interface Auto-close**: Fixed interface auto-close and resource cleanup logic after timeout -- 📱 **Responsive Layout**: Optimized timeout control component display on small screen devices - -## 🔧 Technical Improvements -- 🎛️ **Timeout Control Architecture**: Implemented separated design for frontend countdown timer and backend timeout handling -- 📊 **Image Processing Optimization**: Improved image upload size checking and format validation mechanisms -- 🗂️ **Settings Persistence**: Enhanced settings saving mechanism to ensure correct saving and loading of user preferences -- 🧰 **Tool Script Enhancement**: Added cross-platform cache cleanup tool with support for force cleanup and preview modes - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Related PRs: #22 (Timeout Control Feature), #19 (Image Settings Feature) diff --git a/RELEASE_NOTES/v2.2.3/zh-CN.md b/RELEASE_NOTES/v2.2.3/zh-CN.md deleted file mode 100644 index 766aace..0000000 --- a/RELEASE_NOTES/v2.2.3/zh-CN.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - 超时控制与图片设置增强 - -## 🌟 亮点 -本版本新增了用户可控制的超时设置功能,以及灵活的图片上传设置选项,同时完善了 UV Cache 管理工具,提升整体使用体验。 - -## ✨ 新功能 -- ⏰ **用户超时控制**: 新增可自定义的超时设置功能,支持 30 秒至 2 小时的弹性设置 -- ⏱️ **倒数计时器**: 界面顶部显示实时倒数计时器,提供可视化的时间提醒 -- 🖼️ **图片大小限制**: 新增图片上传大小限制设置(无限制/1MB/3MB/5MB) -- 🔧 **Base64 兼容模式**: 新增 Base64 详细模式,提升部分 AI 模型的图片识别兼容性 -- 🧹 **UV Cache 管理工具**: 新增 `cleanup_cache.py` 脚本,协助管理和清理 UV cache 空间 - -## 🚀 改进功能 -- 📚 **文档结构优化**: 重新整理文档目录结构,将图片移至 `docs/{语言}/images/` 路径 -- 📖 **Cache 管理指南**: 新增详细的 UV Cache 管理指南,包含自动化清理方案 -- 🎯 **智能兼容性提示**: 当图片上传失败时自动显示 Base64 兼容模式建议 -- 🔄 **设置同步机制**: 改进图片设置在不同界面模式间的同步机制 - -## 🐛 问题修复 -- 🛡️ **超时处理优化**: 改进用户自定义超时与 MCP 系统超时的协调机制 -- 🖥️ **界面自动关闭**: 修复超时后界面自动关闭和资源清理逻辑 -- 📱 **响应式布局**: 优化超时控制组件在小屏幕设备上的显示效果 - -## 🔧 技术改进 -- 🎛️ **超时控制架构**: 实现前端倒数计时器与后端超时处理的分离设计 -- 📊 **图片处理优化**: 改进图片上传的大小检查和格式验证机制 -- 🗂️ **设置持久化**: 增强设置保存机制,确保用户偏好的正确保存和载入 -- 🧰 **工具脚本增强**: 新增跨平台的 cache 清理工具,支持强制清理和预览模式 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 相关 PR: #22 (超时控制功能), #19 (图片设置功能) diff --git a/RELEASE_NOTES/v2.2.3/zh-TW.md b/RELEASE_NOTES/v2.2.3/zh-TW.md deleted file mode 100644 index ce761b4..0000000 --- a/RELEASE_NOTES/v2.2.3/zh-TW.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - 超時控制與圖片設定增強 - -## 🌟 亮點 -本版本新增了用戶可控制的超時設定功能,以及靈活的圖片上傳設定選項,同時完善了 UV Cache 管理工具,提升整體使用體驗。 - -## ✨ 新功能 -- ⏰ **用戶超時控制**: 新增可自訂的超時設定功能,支援 30 秒至 2 小時的彈性設定 -- ⏱️ **倒數計時器**: 介面頂部顯示即時倒數計時器,提供視覺化的時間提醒 -- 🖼️ **圖片大小限制**: 新增圖片上傳大小限制設定(無限制/1MB/3MB/5MB) -- 🔧 **Base64 相容模式**: 新增 Base64 詳細模式,提升部分 AI 模型的圖片識別相容性 -- 🧹 **UV Cache 管理工具**: 新增 `cleanup_cache.py` 腳本,協助管理和清理 UV cache 空間 - -## 🚀 改進功能 -- 📚 **文檔結構優化**: 重新整理文檔目錄結構,將圖片移至 `docs/{語言}/images/` 路徑 -- 📖 **Cache 管理指南**: 新增詳細的 UV Cache 管理指南,包含自動化清理方案 -- 🎯 **智能相容性提示**: 當圖片上傳失敗時自動顯示 Base64 相容模式建議 -- 🔄 **設定同步機制**: 改進圖片設定在不同介面模式間的同步機制 - -## 🐛 問題修復 -- 🛡️ **超時處理優化**: 改進用戶自訂超時與 MCP 系統超時的協調機制 -- 🖥️ **介面自動關閉**: 修復超時後介面自動關閉和資源清理邏輯 -- 📱 **響應式佈局**: 優化超時控制元件在小螢幕設備上的顯示效果 - -## 🔧 技術改進 -- 🎛️ **超時控制架構**: 實現前端倒數計時器與後端超時處理的分離設計 -- 📊 **圖片處理優化**: 改進圖片上傳的大小檢查和格式驗證機制 -- 🗂️ **設定持久化**: 增強設定保存機制,確保用戶偏好的正確保存和載入 -- 🧰 **工具腳本增強**: 新增跨平台的 cache 清理工具,支援強制清理和預覽模式 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 相關 PR: #22 (超時控制功能), #19 (圖片設定功能) diff --git a/RELEASE_NOTES/v2.2.4/en.md b/RELEASE_NOTES/v2.2.4/en.md deleted file mode 100644 index 80eba52..0000000 --- a/RELEASE_NOTES/v2.2.4/en.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI Experience Optimization & Bug Fixes - -## 🌟 Highlights -This version focuses on GUI user experience optimization, fixing image copy-paste duplication issues, reorganizing localization file structure, and improving interface text readability. - -## 🐛 Bug Fixes -- 🖼️ **Image Duplicate Paste Fix**: Fixed the issue where Ctrl+V image pasting in GUI would create duplicate images -- 🌐 **Localization Switch Fix**: Fixed image settings area text not translating correctly when switching languages -- 📝 **Font Readability Improvement**: Adjusted font sizes in image settings area for better readability - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.4/zh-CN.md b/RELEASE_NOTES/v2.2.4/zh-CN.md deleted file mode 100644 index 827a39d..0000000 --- a/RELEASE_NOTES/v2.2.4/zh-CN.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI 体验优化与问题修复 - -## 🌟 亮点 -本版本专注于 GUI 使用体验的优化,修复了图片复制粘贴的重复问题,重新组织了语系文件结构,并改善了界面文字的可读性。 - -## 🐛 问题修复 -- 🖼️ **图片重复粘贴修复**: 解决 GUI 界面中使用 Ctrl+V 复制粘贴图片时出现重复粘贴的问题 -- 🌐 **语系切换修复**: 修复图片设定区域在语言切换时文字没有正确翻译的问题 -- 📝 **字体可读性改善**: 调整图片设定区域的字体大小,提升文字可读性 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.4/zh-TW.md b/RELEASE_NOTES/v2.2.4/zh-TW.md deleted file mode 100644 index 05ff546..0000000 --- a/RELEASE_NOTES/v2.2.4/zh-TW.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI 體驗優化與問題修復 - -## 🌟 亮點 -本版本專注於 GUI 使用體驗的優化,修復了圖片複製貼上的重複問題,重新組織了語系檔案結構,並改善了介面文字的可讀性。 - -## 🐛 問題修復 -- 🖼️ **圖片重複貼上修復**: 解決 GUI 介面中使用 Ctrl+V 複製貼上圖片時出現重複貼上的問題 -- 🌐 **語系切換修復**: 修復圖片設定區域在語言切換時文字沒有正確翻譯的問題 -- 📝 **字體可讀性改善**: 調整圖片設定區域的字體大小,提升文字可讀性 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.5/en.md b/RELEASE_NOTES/v2.2.5/en.md deleted file mode 100644 index c7a4033..0000000 --- a/RELEASE_NOTES/v2.2.5/en.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL Environment Support & Cross-Platform Enhancement - -## 🌟 Highlights -This version introduces comprehensive support for WSL (Windows Subsystem for Linux) environments, enabling WSL users to seamlessly use this tool with automatic Windows browser launching, significantly improving cross-platform development experience. - -## ✨ New Features -- 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🔧 **Cross-Platform Testing Enhancement**: Test functionality integrates WSL detection for improved test coverage - -## 🚀 Improvements -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 📊 **System Information Enhancement**: System information tool now displays WSL environment status -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) diff --git a/RELEASE_NOTES/v2.2.5/zh-CN.md b/RELEASE_NOTES/v2.2.5/zh-CN.md deleted file mode 100644 index 717cf73..0000000 --- a/RELEASE_NOTES/v2.2.5/zh-CN.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL 环境支持与跨平台增强 - -## 🌟 亮点 -本版本新增了 WSL (Windows Subsystem for Linux) 环境的完整支持,让 WSL 用户能够无缝使用本工具并自动启动 Windows 浏览器,大幅提升跨平台开发体验。 - -## ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 - -## 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) diff --git a/RELEASE_NOTES/v2.2.5/zh-TW.md b/RELEASE_NOTES/v2.2.5/zh-TW.md deleted file mode 100644 index 666d7e6..0000000 --- a/RELEASE_NOTES/v2.2.5/zh-TW.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL 環境支援與跨平台增強 - -## 🌟 亮點 -本版本新增了 WSL (Windows Subsystem for Linux) 環境的完整支援,讓 WSL 用戶能夠無縫使用本工具並自動啟動 Windows 瀏覽器,大幅提升跨平台開發體驗。 - -## ✨ 新功能 -- 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🔧 **跨平台測試增強**: 測試功能整合 WSL 檢測,提升測試覆蓋率 - -## 🚀 改進功能 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 📊 **系統資訊增強**: 系統資訊工具新增 WSL 環境狀態顯示 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced)