mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +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()
|