mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
335 lines
11 KiB
Python
335 lines
11 KiB
Python
![]() |
#!/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:
|
|||
|
print(f"無法導入 MCP Feedback Enhanced 模組: {e}")
|
|||
|
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:
|
|||
|
print(f"桌面應用程式運行失敗: {e}")
|
|||
|
sys.exit(1)
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
run_desktop_app()
|