diff --git a/.gitignore b/.gitignore index 49c7367..439edfe 100644 --- a/.gitignore +++ b/.gitignore @@ -71,8 +71,9 @@ src-tauri/WixTools/ src-tauri/gen/ # Desktop application binaries (these should be built and copied by build script) -src/mcp_feedback_enhanced/desktop_release/* -src/mcp_feedback_enhanced/desktop_app/ +# Note: desktop_app module should be tracked, only exclude large binaries +src/mcp_feedback_enhanced/desktop_release/*.exe +src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-* src/mcp_feedback_enhanced/desktop/mcp-feedback-enhanced-desktop diff --git a/src/mcp_feedback_enhanced/__main__.py b/src/mcp_feedback_enhanced/__main__.py index 25742fa..4c6ee41 100644 --- a/src/mcp_feedback_enhanced/__main__.py +++ b/src/mcp_feedback_enhanced/__main__.py @@ -276,7 +276,7 @@ def test_desktop_app(): print("✅ 找到發佈包中的桌面應用程式模組") return desktop_func except ImportError: - pass + print("🔍 發佈包中未找到桌面應用程式模組,嘗試開發環境...") # 回退到開發環境路徑 tauri_python_path = os.path.join( @@ -295,12 +295,17 @@ def test_desktop_app(): print("❌ 無法從開發環境路徑導入桌面應用程式模組") return None else: - print(f"⚠️ Tauri Python 模組路徑不存在: {tauri_python_path}") - print("💡 請確保已正確建立 PyTauri 專案結構") + print(f"⚠️ 開發環境路徑不存在: {tauri_python_path}") + print("💡 這可能是 PyPI 安裝的版本,桌面應用功能不可用") return None launch_desktop_app_func = import_desktop_app() if launch_desktop_app_func is None: + print("❌ 桌面應用程式不可用") + print("💡 可能的原因:") + print(" 1. 此版本不包含桌面應用程式二進制檔案") + print(" 2. 請使用包含桌面應用的版本,或使用 Web 模式") + print(" 3. Web 模式指令:uvx mcp-feedback-enhanced test --web") return False print("✅ 桌面應用程式模組導入成功") diff --git a/src/mcp_feedback_enhanced/desktop_app/__init__.py b/src/mcp_feedback_enhanced/desktop_app/__init__.py new file mode 100644 index 0000000..01a63df --- /dev/null +++ b/src/mcp_feedback_enhanced/desktop_app/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +MCP Feedback Enhanced Desktop Application +========================================= + +基於 Tauri 的桌面應用程式包裝器,為 MCP Feedback Enhanced 提供原生桌面體驗。 + +主要功能: +- 原生桌面應用程式界面 +- 整合現有的 Web UI 功能 +- 跨平台支援(Windows、macOS、Linux) +- 無需瀏覽器的獨立運行環境 + +作者: Minidoracat +版本: 2.4.3 +""" + +__version__ = "2.4.3" +__author__ = "Minidoracat" +__email__ = "minidora0702@gmail.com" + +from .desktop_app import DesktopApp, launch_desktop_app + + +__all__ = [ + "DesktopApp", + "__author__", + "__version__", + "launch_desktop_app", +] diff --git a/src/mcp_feedback_enhanced/desktop_app/desktop_app.py b/src/mcp_feedback_enhanced/desktop_app/desktop_app.py new file mode 100644 index 0000000..54e63d4 --- /dev/null +++ b/src/mcp_feedback_enhanced/desktop_app/desktop_app.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +""" +桌面應用程式主要模組 + +此模組提供桌面應用程式的核心功能,包括: +- 桌面模式檢測 +- Tauri 應用程式啟動 +- 與現有 Web UI 的整合 +""" + +import asyncio +import os +import sys +import time + + +# 導入現有的 MCP Feedback Enhanced 模組 +try: + from mcp_feedback_enhanced.debug import server_debug_log as debug_log + from mcp_feedback_enhanced.web.main import WebUIManager, get_web_ui_manager +except ImportError as e: + # 在這裡無法使用 debug_log,因為導入失敗 + import sys + + sys.stderr.write(f"無法導入 MCP Feedback Enhanced 模組: {e}\n") + sys.exit(1) + + +class DesktopApp: + """桌面應用程式管理器""" + + def __init__(self): + self.web_manager: WebUIManager | None = None + self.desktop_mode = False + self.app_handle = None + + def set_desktop_mode(self, enabled: bool = True): + """設置桌面模式""" + self.desktop_mode = enabled + if enabled: + # 設置環境變數,防止開啟瀏覽器 + os.environ["MCP_DESKTOP_MODE"] = "true" + debug_log("桌面模式已啟用,將禁止開啟瀏覽器") + else: + os.environ.pop("MCP_DESKTOP_MODE", None) + debug_log("桌面模式已禁用") + + def is_desktop_mode(self) -> bool: + """檢查是否為桌面模式""" + return ( + self.desktop_mode + or os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true" + ) + + async def start_web_backend(self) -> str: + """啟動 Web 後端服務""" + debug_log("啟動 Web 後端服務...") + + # 獲取 Web UI 管理器 + self.web_manager = get_web_ui_manager() + + # 設置桌面模式,禁止自動開啟瀏覽器 + self.set_desktop_mode(True) + + # 啟動服務器 + if ( + self.web_manager.server_thread is None + or not self.web_manager.server_thread.is_alive() + ): + self.web_manager.start_server() + + # 等待服務器啟動 + max_wait = 10 # 最多等待 10 秒 + wait_count = 0 + while wait_count < max_wait: + if ( + self.web_manager.server_thread + and self.web_manager.server_thread.is_alive() + ): + break + await asyncio.sleep(0.5) + wait_count += 0.5 + + if not ( + self.web_manager.server_thread and self.web_manager.server_thread.is_alive() + ): + raise RuntimeError("Web 服務器啟動失敗") + + server_url = self.web_manager.get_server_url() + debug_log(f"Web 後端服務已啟動: {server_url}") + return server_url + + def create_test_session(self): + """創建測試會話""" + if not self.web_manager: + raise RuntimeError("Web 管理器未初始化") + + import tempfile + + with tempfile.TemporaryDirectory() as temp_dir: + session_id = self.web_manager.create_session( + temp_dir, "桌面應用程式測試 - 驗證 Tauri 整合功能" + ) + debug_log(f"測試會話已創建: {session_id}") + return session_id + + async def launch_tauri_app(self, server_url: str): + """啟動 Tauri 桌面應用程式""" + debug_log("正在啟動 Tauri 桌面視窗...") + + import os + import subprocess + from pathlib import Path + + # 找到 Tauri 可執行檔案 + # 首先嘗試從打包後的位置找(PyPI 安裝後的位置) + try: + from mcp_feedback_enhanced.desktop_release import __file__ as desktop_init + + desktop_dir = Path(desktop_init).parent + + # 根據平台選擇對應的二進制文件 + import platform + + system = platform.system().lower() + machine = platform.machine().lower() + + # 定義平台到二進制文件的映射 + if system == "windows": + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop.exe" + elif system == "darwin": # macOS + # 檢測 Apple Silicon 或 Intel + if machine in ["arm64", "aarch64"]: + tauri_exe = ( + desktop_dir / "mcp-feedback-enhanced-desktop-macos-arm64" + ) + else: + tauri_exe = ( + desktop_dir / "mcp-feedback-enhanced-desktop-macos-intel" + ) + elif system == "linux": + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop-linux" + else: + # 回退到通用名稱 + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop" + + if tauri_exe.exists(): + debug_log(f"找到打包後的 Tauri 可執行檔案: {tauri_exe}") + else: + # 嘗試回退選項 + fallback_files = [ + desktop_dir / "mcp-feedback-enhanced-desktop.exe", + desktop_dir / "mcp-feedback-enhanced-desktop-macos-intel", + desktop_dir / "mcp-feedback-enhanced-desktop-macos-arm64", + desktop_dir / "mcp-feedback-enhanced-desktop-linux", + desktop_dir / "mcp-feedback-enhanced-desktop", + ] + + for fallback in fallback_files: + if fallback.exists(): + tauri_exe = fallback + debug_log(f"使用回退的可執行檔案: {tauri_exe}") + break + else: + raise FileNotFoundError( + f"找不到任何可執行檔案,檢查的路徑: {tauri_exe}" + ) + + except (ImportError, FileNotFoundError): + # 回退到開發環境路徑 + debug_log("未找到打包後的可執行檔案,嘗試開發環境路徑...") + project_root = Path(__file__).parent.parent.parent.parent + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "debug" + / "mcp-feedback-enhanced-desktop.exe" + ) + + if not tauri_exe.exists(): + # 嘗試其他可能的路徑 + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "debug" + / "mcp-feedback-enhanced-desktop" + ) + + if not tauri_exe.exists(): + # 嘗試 release 版本 + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "release" + / "mcp-feedback-enhanced-desktop.exe" + ) + if not tauri_exe.exists(): + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "release" + / "mcp-feedback-enhanced-desktop" + ) + + if not tauri_exe.exists(): + raise FileNotFoundError( + "找不到 Tauri 可執行檔案,已嘗試的路徑包括開發和發布目錄" + ) from None + + debug_log(f"找到 Tauri 可執行檔案: {tauri_exe}") + + # 設置環境變數 + env = os.environ.copy() + env["MCP_DESKTOP_MODE"] = "true" + env["MCP_WEB_URL"] = server_url + + # 啟動 Tauri 應用程式 + try: + # Windows 下隱藏控制台視窗 + creation_flags = 0 + if os.name == "nt": + creation_flags = subprocess.CREATE_NO_WINDOW + + self.app_handle = subprocess.Popen( + [str(tauri_exe)], + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=creation_flags, + ) + debug_log("Tauri 桌面應用程式已啟動") + + # 等待一下確保應用程式啟動 + await asyncio.sleep(2) + + except Exception as e: + debug_log(f"啟動 Tauri 應用程式失敗: {e}") + raise + + def stop(self): + """停止桌面應用程式""" + debug_log("正在停止桌面應用程式...") + + # 停止 Tauri 應用程式 + if self.app_handle: + try: + self.app_handle.terminate() + self.app_handle.wait(timeout=5) + debug_log("Tauri 應用程式已停止") + except Exception as e: + debug_log(f"停止 Tauri 應用程式時發生錯誤: {e}") + try: + self.app_handle.kill() + except: + pass + finally: + self.app_handle = None + + if self.web_manager: + # 注意:不停止 Web 服務器,保持持久性 + debug_log("Web 服務器保持運行狀態") + + # 注意:不清除桌面模式設置,保持 MCP_DESKTOP_MODE 環境變數 + # 這樣下次 MCP 調用時仍然會啟動桌面應用程式 + # self.set_desktop_mode(False) # 註釋掉這行 + debug_log("桌面應用程式已停止") + + +async def launch_desktop_app(test_mode: bool = False) -> DesktopApp: + """啟動桌面應用程式 + + Args: + test_mode: 是否為測試模式,測試模式下會創建測試會話 + """ + debug_log("正在啟動桌面應用程式...") + + app = DesktopApp() + + try: + # 啟動 Web 後端 + server_url = await app.start_web_backend() + + if test_mode: + # 測試模式:創建測試會話 + debug_log("測試模式:創建測試會話") + app.create_test_session() + else: + # MCP 調用模式:使用現有會話 + debug_log("MCP 調用模式:使用現有 MCP 會話,不創建新的測試會話") + + # 啟動 Tauri 桌面應用程式 + await app.launch_tauri_app(server_url) + + debug_log(f"桌面應用程式已啟動,後端服務: {server_url}") + return app + + except Exception as e: + debug_log(f"桌面應用程式啟動失敗: {e}") + app.stop() + raise + + +def run_desktop_app(): + """同步方式運行桌面應用程式""" + try: + # 設置事件循環策略(Windows) + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + # 運行應用程式 + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + app = loop.run_until_complete(launch_desktop_app()) + + # 保持應用程式運行 + debug_log("桌面應用程式正在運行,按 Ctrl+C 停止...") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + debug_log("收到停止信號...") + finally: + app.stop() + loop.close() + + except Exception as e: + debug_log(f"桌面應用程式運行失敗: {e}") + sys.exit(1) + + +if __name__ == "__main__": + run_desktop_app() diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index f05f62c..f99fe29 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -613,7 +613,7 @@ class WebUIManager: debug_log("使用發佈包中的桌面應用程式模組") return desktop_func except ImportError: - pass + debug_log("發佈包中未找到桌面應用程式模組,嘗試開發環境...") # 回退到開發環境路徑 import sys @@ -633,6 +633,7 @@ class WebUIManager: return dev_func except ImportError: debug_log("無法從開發環境路徑導入桌面應用程式模組") + debug_log("這可能是 PyPI 安裝的版本,桌面應用功能不可用") raise launch_desktop_app_func = import_desktop_app()