🔥 移除 Electron 桌面

This commit is contained in:
Minidoracat 2025-06-12 04:24:38 +08:00
parent c3a49695e7
commit a9a3139f0e
13 changed files with 9 additions and 2144 deletions

23
.gitignore vendored
View File

@ -17,30 +17,7 @@ htmlcov/
.venv*/
venv*/
# Node.js / Electron (Desktop App)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
yarn.lock
.npm
.yarn-integrity
# Electron build outputs
dist-desktop/
out/
*.dmg
*.exe
*.deb
*.rpm
*.AppImage
*.zip
*.tar.gz
# Electron cache
.electron/
.electron-gyp/
# Logs
*.log

View File

@ -1,154 +0,0 @@
{
"appId": "com.minidoracat.mcp-feedback-enhanced",
"productName": "MCP Feedback Enhanced",
"copyright": "Copyright © 2024 Minidoracat",
"directories": {
"app": "src/mcp_feedback_enhanced/desktop",
"output": "dist-desktop",
"buildResources": "src/mcp_feedback_enhanced/desktop/assets"
},
"files": [
"main.js",
"preload.js",
"package.json",
"node_modules/**/*"
],
"extraResources": [
{
"from": "src/mcp_feedback_enhanced",
"to": "app",
"filter": [
"**/*",
"!desktop/node_modules/**/*",
"!desktop/dist/**/*",
"!**/*.pyc",
"!**/__pycache__/**/*",
"!**/test_*.py"
]
}
],
"compression": "normal",
"removePackageScripts": true,
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "src/mcp_feedback_enhanced/desktop/assets/icon.ico",
"requestedExecutionLevel": "asInvoker",
"artifactName": "${productName}-${version}-${arch}.${ext}",
"publisherName": "Minidoracat"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "src/mcp_feedback_enhanced/desktop/assets/icon.icns",
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
}
],
"icon": "src/mcp_feedback_enhanced/desktop/assets/icon.png",
"category": "Development",
"artifactName": "${productName}-${version}-${arch}.${ext}",
"desktop": {
"StartupNotify": "true",
"Encoding": "UTF-8",
"MimeType": "x-scheme-handler/mcp-feedback",
"Keywords": "mcp;feedback;ai;development"
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"allowElevation": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "MCP Feedback Enhanced",
"include": "build/installer.nsh",
"artifactName": "${productName}-Setup-${version}.${ext}",
"deleteAppDataOnUninstall": false
},
"dmg": {
"title": "${productName} ${version}",
"icon": "src/mcp_feedback_enhanced/desktop/assets/icon.icns",
"background": "src/mcp_feedback_enhanced/desktop/assets/dmg-background.png",
"contents": [
{
"x": 130,
"y": 220,
"type": "file"
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
},
"artifactName": "${productName}-${version}.${ext}"
},
"appImage": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"deb": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"priority": "optional",
"depends": [
"gconf2",
"gconf-service",
"libnotify4",
"libappindicator1",
"libxtst6",
"libnss3"
]
},
"rpm": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"depends": [
"libXScrnSaver",
"libnotify",
"libnss3"
]
},
"publish": {
"provider": "github",
"owner": "minidoracat",
"repo": "mcp-feedback-enhanced",
"releaseType": "draft"
},
"buildVersion": "2.3.0"
}

View File

@ -1,13 +1,13 @@
[project]
name = "mcp-feedback-enhanced"
version = "2.3.0"
description = "Enhanced MCP server for interactive user feedback and command execution in AI-assisted development, featuring Web UI with intelligent environment detection and optional Electron desktop application."
description = "Enhanced MCP server for interactive user feedback and command execution in AI-assisted development, featuring Web UI with intelligent environment detection."
readme = "README.md"
requires-python = ">=3.11"
authors = [
{ name = "Minidoracat", email = "minidora0702@gmail.com" }
]
keywords = ["mcp", "ai", "feedback", "web-ui", "interactive", "development", "electron", "desktop", "cross-platform"]
keywords = ["mcp", "ai", "feedback", "web-ui", "interactive", "development"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
@ -34,10 +34,6 @@ dev = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
]
desktop = [
# Node.js 環境需要系統安裝,不是 Python 套件
# 請確保系統已安裝 Node.js >= 16.0.0
]
[project.urls]
Homepage = "https://github.com/Minidoracat/mcp-feedback-enhanced"
@ -99,7 +95,7 @@ exclude = [
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"*.egg-info",
".trunk",
@ -268,7 +264,7 @@ exclude = [
".venv/",
"venv/",
".trunk/",
"node_modules/",
".mypy_cache/",
]

View File

@ -47,15 +47,6 @@ def main():
test_parser.add_argument(
"--web", action="store_true", help="測試 Web UI (自動持續運行)"
)
test_parser.add_argument(
"--desktop", action="store_true", help="測試桌面應用 (啟動 Electron 應用)"
)
test_parser.add_argument(
"--full", action="store_true", help="完整整合測試 (Web + 桌面)"
)
test_parser.add_argument(
"--electron-only", action="store_true", help="僅測試 Electron 環境"
)
test_parser.add_argument(
"--timeout", type=int, default=60, help="測試超時時間 (秒)"
)
@ -109,28 +100,10 @@ def run_tests(args):
success = test_web_ui_simple()
if not success:
sys.exit(1)
elif args.desktop:
print("🧪 執行桌面應用測試...")
success = test_desktop_app()
if not success:
sys.exit(1)
elif args.full:
print("🧪 執行完整整合測試...")
success = test_full_integration()
if not success:
sys.exit(1)
elif args.electron_only:
print("🧪 執行 Electron 環境測試...")
success = test_electron_environment()
if not success:
sys.exit(1)
else:
print("❌ 測試功能已簡化")
print("💡 可用的測試選項:")
print(" --web 測試 Web UI")
print(" --desktop 測試桌面應用")
print(" --full 完整整合測試")
print(" --electron-only 僅測試 Electron 環境")
print("💡 對於開發者:使用 'uv run pytest' 執行完整測試")
sys.exit(1)
@ -212,68 +185,6 @@ def test_web_ui_simple():
os.environ.pop("MCP_WEB_PORT", None)
def test_desktop_app():
"""測試桌面應用"""
try:
print("🔧 檢查桌面環境...")
# 檢查桌面環境可用性
from .desktop import is_desktop_available
if not is_desktop_available():
print("❌ 桌面環境不可用")
print("💡 請確保 Node.js 已安裝且不在遠程環境中")
return False
print("✅ 桌面環境檢查通過")
# 設置測試環境變數,避免端口衝突和權限問題
os.environ["MCP_TEST_MODE"] = "true"
os.environ["MCP_WEB_PORT"] = "9767" # 使用不同端口避免與 Web 測試衝突
os.environ["MCP_FEEDBACK_MODE"] = "desktop"
print("🔧 創建 Electron 管理器...")
import asyncio
async def run_desktop_test():
print("🚀 啟動完整桌面應用測試...")
print("💡 這將啟動 Web 服務器和 Electron 應用視窗")
print("💡 請在應用中測試基本功能,然後關閉視窗")
# 使用完整的桌面應用啟動函數
from .desktop import launch_desktop_app
try:
# 這會啟動 Web 服務器和 Electron 應用
result = await launch_desktop_app(
os.getcwd(),
"桌面應用測試 - 驗證 Electron 整合功能",
300, # 5分鐘超時
)
print("✅ 桌面應用測試完成")
print(f"收到回饋: {result.get('interactive_feedback', '無回饋')}")
return True
except Exception as e:
print(f"❌ 桌面應用測試失敗: {e}")
return False
return asyncio.run(run_desktop_test())
except Exception as e:
print(f"❌ 桌面應用測試失敗: {e}")
import traceback
traceback.print_exc()
return False
finally:
# 清理測試環境變數
os.environ.pop("MCP_TEST_MODE", None)
os.environ.pop("MCP_WEB_PORT", None)
os.environ.pop("MCP_FEEDBACK_MODE", None)
async def wait_for_process(process):
"""等待進程結束"""
try:
@ -295,162 +206,6 @@ async def wait_for_process(process):
print(f"等待進程時出錯: {e}")
def test_electron_environment():
"""測試 Electron 環境"""
try:
print("🔧 檢查 Electron 環境...")
# 檢查 Node.js
import subprocess
try:
result = subprocess.run(
["node", "--version"],
capture_output=True,
text=True,
timeout=10,
check=False,
)
if result.returncode == 0:
print(f"✅ Node.js 版本: {result.stdout.strip()}")
else:
print("❌ Node.js 不可用")
return False
except (subprocess.TimeoutExpired, FileNotFoundError):
print("❌ Node.js 不可用")
return False
# 檢查桌面模組
from .desktop import is_desktop_available
if is_desktop_available():
print("✅ 桌面環境可用")
else:
print("❌ 桌面環境不可用")
return False
# 檢查 Electron 管理器
from .desktop.electron_manager import ElectronManager
manager = ElectronManager()
if manager.is_electron_available():
print("✅ Electron 環境可用")
else:
print("❌ Electron 環境不可用")
return False
# 檢查文件結構
desktop_dir = manager.desktop_dir
required_files = ["main.js", "preload.js", "package.json"]
for file_name in required_files:
file_path = desktop_dir / file_name
if file_path.exists():
print(f"{file_name} 存在")
else:
print(f"{file_name} 不存在")
return False
# 檢查 node_modules
node_modules = desktop_dir / "node_modules"
if node_modules.exists():
print("✅ node_modules 存在")
else:
print("❌ node_modules 不存在")
return False
print("🎉 Electron 環境測試完成,所有檢查通過")
return True
except Exception as e:
print(f"❌ Electron 環境測試失敗: {e}")
return False
def test_full_integration():
"""完整整合測試"""
try:
print("🧪 執行完整整合測試...")
# 1. 環境變數測試
print("\n📋 1. 測試環境變數控制...")
test_cases = [("auto", "auto"), ("web", "web"), ("desktop", "desktop")]
for env_value, expected in test_cases:
os.environ["MCP_FEEDBACK_MODE"] = env_value
# 重新導入以獲取新的環境變數值
import sys
if "mcp_feedback_enhanced.server" in sys.modules:
del sys.modules["mcp_feedback_enhanced.server"]
from .server import get_feedback_mode
actual = get_feedback_mode().value
if actual == expected:
print(f" ✅ MCP_FEEDBACK_MODE='{env_value}'{actual}")
else:
print(
f" ❌ MCP_FEEDBACK_MODE='{env_value}'{actual} (期望: {expected})"
)
return False
# 2. Electron 環境測試
print("\n📋 2. 測試 Electron 環境...")
if not test_electron_environment():
print("❌ Electron 環境測試失敗")
return False
# 3. Web UI 基本功能測試
print("\n📋 3. 測試 Web UI 基本功能...")
import tempfile
from .web.main import WebUIManager
# 設置測試模式
os.environ["MCP_TEST_MODE"] = "true"
os.environ["MCP_WEB_PORT"] = "9766"
with tempfile.TemporaryDirectory() as temp_dir:
manager = WebUIManager(host="127.0.0.1") # 使用動態端口分配避免衝突
session_id = manager.create_session(temp_dir, "整合測試會話")
if session_id:
print(" ✅ Web UI 會話創建成功")
else:
print(" ❌ Web UI 會話創建失敗")
return False
# 4. 桌面模式檢測測試
print("\n📋 4. 測試桌面模式檢測...")
os.environ["MCP_FEEDBACK_MODE"] = "desktop"
manager = WebUIManager()
if manager.should_use_desktop_mode():
print(" ✅ 桌面模式檢測正常")
else:
print(" ❌ 桌面模式檢測失敗")
return False
print("\n🎉 完整整合測試通過!")
print("💡 所有核心功能正常運作")
return True
except Exception as e:
print(f"❌ 完整整合測試失敗: {e}")
import traceback
traceback.print_exc()
return False
finally:
# 清理測試環境變數
os.environ.pop("MCP_TEST_MODE", None)
os.environ.pop("MCP_WEB_PORT", None)
def show_version():
"""顯示版本資訊"""
from . import __author__, __version__

View File

@ -1,282 +0,0 @@
#!/usr/bin/env python3
"""
桌面應用模組
===========
此模組提供 Electron 桌面應用整合功能實現零前端改動的桌面化方案
主要功能
- Electron 進程管理
- 與現有 Web UI 的無縫整合
- 跨平台桌面應用支援
- 環境變數控制
作者: Augment Agent
版本: 2.3.0
"""
import asyncio
import os
import sys
import time
from typing import Optional
from ..debug import web_debug_log as debug_log
def is_desktop_available() -> bool:
"""
檢測桌面環境是否可用
Returns:
bool: True 表示桌面環境可用
"""
try:
# 檢查是否有 Node.js 環境
import subprocess
result = subprocess.run(
["node", "--version"],
capture_output=True,
text=True,
timeout=5,
check=False,
)
if result.returncode != 0:
debug_log("Node.js 不可用,桌面模式不可用")
return False
# 檢查是否為遠程環境
from ..server import is_remote_environment
if is_remote_environment():
debug_log("檢測到遠程環境,桌面模式不適用")
return False
debug_log("桌面環境檢測通過")
return True
except (subprocess.TimeoutExpired, FileNotFoundError, ImportError) as e:
debug_log(f"桌面環境檢測失敗: {e}")
return False
except Exception as e:
debug_log(f"桌面環境檢測出現未預期錯誤: {e}")
return False
async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> dict:
"""
啟動桌面應用收集回饋優化版本支援並行啟動
Args:
project_dir: 專案目錄路徑
summary: AI 工作摘要
timeout: 超時時間
Returns:
dict: 收集到的回饋資料
"""
debug_log("啟動桌面應用(並行優化版本)...")
start_time = time.time()
try:
# 並行任務1創建 Electron 管理器和依賴檢查
async def init_electron_manager():
from .electron_manager import ElectronManager
manager = ElectronManager()
# 預先檢查依賴(如果實現了異步版本)
if hasattr(manager, "ensure_dependencies_async"):
await manager.ensure_dependencies_async()
return manager
# 並行任務2初始化 Web 管理器和會話
async def init_web_manager():
from ..web import get_web_ui_manager
web_manager = get_web_ui_manager()
# 確保異步初始化完成
if hasattr(web_manager, "_init_async_components"):
await web_manager._init_async_components()
# 創建會話
web_manager.create_session(project_dir, summary)
session = web_manager.get_current_session()
if not session:
raise RuntimeError("無法創建回饋會話")
return web_manager, session
# 並行執行初始化任務
debug_log("並行執行初始化任務...")
init_results = await asyncio.gather(
init_electron_manager(), init_web_manager(), return_exceptions=True
)
# 檢查初始化結果
if isinstance(init_results[0], Exception):
raise init_results[0]
if isinstance(init_results[1], Exception):
raise init_results[1]
manager = init_results[0]
web_result = init_results[1]
if isinstance(web_result, tuple) and len(web_result) == 2:
web_manager, session = web_result
else:
raise RuntimeError("Web 管理器初始化返回格式錯誤")
init_time = time.time() - start_time
debug_log(f"並行初始化完成,耗時: {init_time:.2f}")
# 並行任務3啟動 Web 服務器
async def start_web_server():
if (
not web_manager.server_thread
or not web_manager.server_thread.is_alive()
):
debug_log("啟動 Web 服務器...")
web_manager.start_server()
# 智能等待服務器啟動(減少固定等待時間)
await _wait_for_server_startup(web_manager)
return web_manager.port
# 並行任務4準備 Electron 環境
async def prepare_electron():
# 如果有預熱方法,在這裡調用
if hasattr(manager, "preheat_async"):
await manager.preheat_async()
return True
# 並行執行服務器啟動和 Electron 準備
debug_log("並行啟動 Web 服務器和準備 Electron 環境...")
startup_results = await asyncio.gather(
start_web_server(), prepare_electron(), return_exceptions=True
)
if isinstance(startup_results[0], Exception):
raise startup_results[0]
if isinstance(startup_results[1], Exception):
debug_log(f"Electron 預熱失敗(非致命): {startup_results[1]}")
server_port = startup_results[0]
# 類型檢查:確保 manager 不是 Exception
if isinstance(manager, Exception):
raise manager
# 設置 Web 服務器端口
manager.set_web_server_port(server_port) # type: ignore[union-attr]
debug_log(f"桌面應用將連接到: http://localhost:{server_port}")
# 啟動桌面應用
desktop_success = await manager.launch_desktop_app(summary, project_dir) # type: ignore[union-attr]
if desktop_success:
total_startup_time = time.time() - start_time
debug_log(
f"桌面應用啟動成功,總耗時: {total_startup_time:.2f}秒,等待用戶回饋..."
)
try:
# 等待用戶回饋
result = await session.wait_for_feedback(timeout)
debug_log("收到桌面應用用戶回饋")
return result # type: ignore[no-any-return]
finally:
# 確保 Electron 進程被正確清理
debug_log("清理 Electron 進程...")
if not isinstance(manager, Exception):
await manager.cleanup_async() # type: ignore[union-attr]
debug_log("Electron 進程清理完成")
else:
debug_log("桌面應用啟動失敗,回退到 Web 模式")
# 回退到 Web 模式
from ..web import launch_web_feedback_ui
return await launch_web_feedback_ui(project_dir, summary, timeout)
except Exception as e:
debug_log(f"桌面應用啟動過程中出錯: {e}")
debug_log("回退到 Web 模式")
# 確保清理 Electron 進程
try:
if "manager" in locals() and not isinstance(manager, Exception):
await manager.cleanup_async() # type: ignore[union-attr]
except Exception as cleanup_error:
debug_log(f"清理 Electron 進程時出錯: {cleanup_error}")
# 回退到 Web 模式
from ..web import launch_web_feedback_ui
return await launch_web_feedback_ui(project_dir, summary, timeout)
async def _wait_for_server_startup(web_manager, max_wait: int = 10) -> bool:
"""智能等待服務器啟動"""
debug_log("智能等待 Web 服務器啟動...")
for attempt in range(max_wait):
if web_manager.server_thread and web_manager.server_thread.is_alive():
# 嘗試連接測試
try:
import aiohttp
timeout = aiohttp.ClientTimeout(total=1)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(
f"http://{web_manager.host}:{web_manager.port}/"
) as response:
if response.status == 200:
debug_log(
f"✅ Web 服務器啟動驗證成功,耗時: {attempt + 1}"
)
return True
except Exception:
pass
await asyncio.sleep(1)
debug_log(f"等待 Web 服務器啟動... ({attempt + 1}/{max_wait})")
# 回退到線程檢查
if web_manager.server_thread and web_manager.server_thread.is_alive():
debug_log("✅ Web 服務器線程運行正常(回退驗證)")
return True
debug_log("❌ Web 服務器啟動失敗")
return False
class ElectronManager:
"""Electron 管理器 - 預留接口"""
def __init__(self):
"""初始化 Electron 管理器"""
self.electron_process = None
self.web_server_port = None
debug_log("ElectronManager 初始化(預留實現)")
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
"""
啟動桌面應用
Args:
summary: AI 工作摘要
project_dir: 專案目錄
Returns:
bool: 啟動是否成功
"""
debug_log("桌面應用啟動功能尚未實現")
debug_log("此功能將在階段 2 中實現")
return False
def is_available(self) -> bool:
"""檢查桌面管理器是否可用"""
return is_desktop_available()
# 主要導出介面
__all__ = ["ElectronManager", "is_desktop_available", "launch_desktop_app"]

View File

@ -1,39 +0,0 @@
# 應用圖標資源
此目錄包含桌面應用的圖標資源文件。
## 需要的圖標文件
### Windows
- `icon.ico` - Windows 圖標文件多尺寸16x16, 32x32, 48x48, 256x256
### macOS
- `icon.icns` - macOS 圖標文件多尺寸16x16 到 1024x1024
- `entitlements.mac.plist` - macOS 權限配置文件
### Linux
- `icon.png` - Linux 圖標文件(建議 512x512
### 打包資源
- `dmg-background.png` - macOS DMG 背景圖片540x380
## 圖標設計建議
- 使用 MCP Feedback Enhanced 的品牌色彩
- 簡潔明了的設計,在小尺寸下仍然清晰
- 符合各平台的設計規範
- 建議使用矢量圖形作為源文件
## 臨時解決方案
在開發階段,可以使用以下方式創建基本圖標:
1. 使用線上圖標生成器
2. 從現有的 Web UI favicon 轉換
3. 使用系統預設圖標
## 注意事項
- 圖標文件應該放在此目錄中
- electron-builder 會自動處理圖標的打包
- 確保圖標文件的版權合規性

View File

@ -1,28 +0,0 @@
# 圖標佔位符
此文件標記圖標資源的位置。在實際部署中,需要替換為真實的圖標文件:
## 需要的圖標文件:
### Windows
- icon.ico (多尺寸16x16, 32x32, 48x48, 256x256)
### macOS
- icon.icns (多尺寸16x16 到 1024x1024)
### Linux
- icon.png (建議 512x512)
## 臨時解決方案
在開發階段,可以:
1. 使用 Electron 預設圖標
2. 從現有 Web UI 的 favicon 轉換
3. 使用線上圖標生成器創建基本圖標
## 圖標設計建議
- 簡潔明了,體現 MCP 和回饋收集的概念
- 在小尺寸下仍然清晰可辨
- 符合各平台的設計規範
- 使用一致的色彩方案

View File

@ -1,514 +0,0 @@
#!/usr/bin/env python3
"""
Electron 管理器
==============
此模組負責管理 Electron 進程的生命週期包括
- Electron 應用啟動和停止
- Web 服務器的整合
- 依賴檢測和自動安裝
- 進程管理和錯誤處理
此文件為階段 1 的預留實現完整功能將在階段 2 中實現
作者: Augment Agent
版本: 2.3.0
"""
import asyncio
import os
import platform
import signal
import subprocess
import warnings
from pathlib import Path
from ..debug import web_debug_log as debug_log
from ..utils.error_handler import ErrorHandler, ErrorType
def suppress_windows_asyncio_warnings():
"""抑制 Windows 上的 asyncio 相關警告"""
if platform.system().lower() == "windows":
# 抑制 ResourceWarning 和 asyncio 相關警告
warnings.filterwarnings("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", message=".*unclosed transport.*")
warnings.filterwarnings("ignore", message=".*I/O operation on closed pipe.*")
# 設置環境變數抑制 asyncio 警告
os.environ.setdefault("PYTHONWARNINGS", "ignore::ResourceWarning")
debug_log("已設置 Windows asyncio 警告抑制")
class ElectronManager:
"""Electron 進程管理器 - 跨平台支持"""
def __init__(self):
"""初始化 Electron 管理器"""
self.electron_process: asyncio.subprocess.Process | None = None
self.desktop_dir = Path(__file__).parent
self.web_server_port: int | None = None
self.platform = platform.system().lower()
self._cleanup_in_progress = False
# 設置平台特定的警告抑制
suppress_windows_asyncio_warnings()
debug_log("ElectronManager 初始化完成")
debug_log(f"桌面模組目錄: {self.desktop_dir}")
debug_log(f"檢測到平台: {self.platform}")
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
"""
啟動 Electron 桌面應用
Args:
summary: AI 工作摘要
project_dir: 專案目錄
Returns:
bool: 啟動是否成功
"""
debug_log("=== 桌面應用啟動 ===")
debug_log(f"摘要: {summary}")
debug_log(f"專案目錄: {project_dir}")
try:
# 確保依賴已安裝
if not await self.ensure_dependencies():
debug_log("依賴檢查失敗,無法啟動桌面應用")
return False
# 啟動 Electron 應用
success = await self._start_electron_process()
if success:
debug_log("Electron 桌面應用啟動成功")
return True
debug_log("Electron 桌面應用啟動失敗")
return False
except Exception as e:
error_id = ErrorHandler.log_error_with_context(
e,
context={"operation": "桌面應用啟動", "project_dir": project_dir},
error_type=ErrorType.SYSTEM,
)
debug_log(f"桌面應用啟動異常 [錯誤ID: {error_id}]: {e}")
return False
def set_web_server_port(self, port: int):
"""設置 Web 服務器端口"""
self.web_server_port = port
debug_log(f"設置 Web 服務器端口: {port}")
def is_electron_available(self) -> bool:
"""檢查 Electron 是否可用"""
try:
# 檢查 Node.js
result = subprocess.run(
["node", "--version"],
capture_output=True,
text=True,
timeout=5,
check=False,
)
if result.returncode != 0:
debug_log("Node.js 不可用")
return False
debug_log(f"Node.js 版本: {result.stdout.strip()}")
# 檢查 package.json 是否存在
package_json = self.desktop_dir / "package.json"
if not package_json.exists():
debug_log("package.json 不存在,需要在階段 2 中創建")
return False
return True
except Exception as e:
debug_log(f"Electron 可用性檢查失敗: {e}")
return False
async def ensure_dependencies(self) -> bool:
"""確保依賴已安裝"""
debug_log("檢查 Electron 依賴...")
try:
# 檢查 package.json 是否存在
package_json = self.desktop_dir / "package.json"
if not package_json.exists():
debug_log("package.json 不存在,創建中...")
await self._create_package_json()
# 檢查 node_modules 是否存在
node_modules = self.desktop_dir / "node_modules"
if not node_modules.exists():
debug_log("node_modules 不存在,安裝依賴中...")
if not await self._install_dependencies():
return False
# 檢查 main.js 是否存在
main_js = self.desktop_dir / "main.js"
if not main_js.exists():
debug_log("main.js 不存在,將在後續步驟中創建")
return False
debug_log("所有依賴檢查通過")
return True
except Exception as e:
error_id = ErrorHandler.log_error_with_context(
e, context={"operation": "依賴檢查"}, error_type=ErrorType.DEPENDENCY
)
debug_log(f"依賴檢查失敗 [錯誤ID: {error_id}]: {e}")
return False
async def ensure_dependencies_async(self) -> bool:
"""異步確保依賴已安裝(別名方法)"""
return await self.ensure_dependencies()
def cleanup(self):
"""同步清理資源(向後兼容)"""
try:
# 在事件循環中運行異步清理
loop = asyncio.get_event_loop()
if loop.is_running():
# 如果事件循環正在運行,創建任務
asyncio.create_task(self.cleanup_async())
else:
# 如果沒有事件循環,運行異步清理
asyncio.run(self.cleanup_async())
except Exception as e:
debug_log(f"同步清理失敗,嘗試基本清理: {e}")
self._basic_cleanup()
async def cleanup_async(self):
"""異步清理資源 - 跨平台支持"""
if self._cleanup_in_progress or not self.electron_process:
return
self._cleanup_in_progress = True
debug_log(f"開始清理 Electron 進程 (平台: {self.platform})")
try:
# 檢查進程是否還在運行
if self.electron_process.returncode is None:
await self._terminate_process_cross_platform()
else:
debug_log("Electron 進程已自然結束")
# 等待進程完全結束並清理管道
await self._wait_and_cleanup_pipes()
except Exception as e:
debug_log(f"清理 Electron 進程時出錯: {e}")
# 嘗試強制清理
await self._force_cleanup()
finally:
self.electron_process = None
self._cleanup_in_progress = False
debug_log("Electron 進程清理完成")
async def _terminate_process_cross_platform(self):
"""跨平台進程終止"""
if self.platform == "windows":
await self._terminate_windows()
else:
await self._terminate_unix()
async def _terminate_windows(self):
"""Windows 平台進程終止"""
debug_log("使用 Windows 進程終止策略")
if not self.electron_process:
return
try:
# Windows: 使用 terminate() 然後等待
self.electron_process.terminate()
try:
await asyncio.wait_for(self.electron_process.wait(), timeout=5.0)
debug_log("Electron 進程已優雅終止")
except TimeoutError:
debug_log("優雅終止超時,強制終止")
self.electron_process.kill()
await asyncio.wait_for(self.electron_process.wait(), timeout=3.0)
debug_log("Electron 進程已強制終止")
except Exception as e:
debug_log(f"Windows 進程終止失敗: {e}")
raise
async def _terminate_unix(self):
"""Unix 系統進程終止"""
debug_log("使用 Unix 進程終止策略")
if not self.electron_process:
return
try:
# Unix: 發送 SIGTERM 然後等待
self.electron_process.send_signal(signal.SIGTERM)
try:
await asyncio.wait_for(self.electron_process.wait(), timeout=5.0)
debug_log("Electron 進程已優雅終止")
except TimeoutError:
debug_log("優雅終止超時,發送 SIGKILL")
# 使用 getattr 來處理可能不存在的 SIGKILL
sigkill = getattr(signal, "SIGKILL", signal.SIGTERM)
self.electron_process.send_signal(sigkill)
await asyncio.wait_for(self.electron_process.wait(), timeout=3.0)
debug_log("Electron 進程已強制終止")
except Exception as e:
debug_log(f"Unix 進程終止失敗: {e}")
raise
async def _wait_and_cleanup_pipes(self):
"""等待進程結束並清理管道"""
if not self.electron_process:
return
try:
# 確保進程已經結束
if self.electron_process.returncode is None:
await self.electron_process.wait()
# 平台特定的管道清理
if self.platform == "windows":
await self._cleanup_pipes_windows()
else:
await self._cleanup_pipes_unix()
except Exception as e:
debug_log(f"管道清理失敗: {e}")
async def _cleanup_pipes_windows(self):
"""Windows 平台管道清理"""
debug_log("清理 Windows 管道")
if not self.electron_process:
return
try:
# Windows: 需要特殊處理 asyncio 管道
pipes = [
("stdout", self.electron_process.stdout),
("stderr", self.electron_process.stderr),
("stdin", self.electron_process.stdin),
]
for pipe_name, pipe in pipes:
if pipe and hasattr(pipe, "close"):
try:
# 檢查是否有 is_closing 方法(某些管道類型可能沒有)
if not hasattr(pipe, "is_closing") or not pipe.is_closing():
pipe.close()
# 等待管道關閉
if hasattr(pipe, "wait_closed"):
await pipe.wait_closed()
debug_log(f"已關閉 {pipe_name} 管道")
except Exception as e:
# Windows 上的管道關閉錯誤通常是無害的
debug_log(f"關閉 {pipe_name} 管道時出現預期錯誤: {e}")
except Exception as e:
debug_log(f"Windows 管道清理失敗: {e}")
async def _cleanup_pipes_unix(self):
"""Unix 系統管道清理"""
debug_log("清理 Unix 管道")
if not self.electron_process:
return
try:
# Unix: 直接關閉管道
pipes = [
("stdout", self.electron_process.stdout),
("stderr", self.electron_process.stderr),
("stdin", self.electron_process.stdin),
]
for pipe_name, pipe in pipes:
if pipe and hasattr(pipe, "close"):
try:
pipe.close()
if hasattr(pipe, "wait_closed"):
await pipe.wait_closed()
debug_log(f"已關閉 {pipe_name} 管道")
except Exception as e:
debug_log(f"關閉 {pipe_name} 管道失敗: {e}")
except Exception as e:
debug_log(f"Unix 管道清理失敗: {e}")
async def _force_cleanup(self):
"""強制清理(最後手段)"""
debug_log("執行強制清理")
try:
if self.electron_process and self.electron_process.returncode is None:
# 嘗試使用 psutil 強制終止
try:
import psutil
process = psutil.Process(self.electron_process.pid)
process.kill()
debug_log("使用 psutil 強制終止進程")
except ImportError:
debug_log("psutil 不可用,使用系統調用")
if self.electron_process:
if self.platform == "windows":
self.electron_process.kill()
else:
# 使用 getattr 來處理可能不存在的 SIGKILL
sigkill = getattr(signal, "SIGKILL", signal.SIGTERM)
self.electron_process.send_signal(sigkill)
except Exception as e:
debug_log(f"強制終止失敗: {e}")
# 基本清理
self._basic_cleanup()
except Exception as e:
debug_log(f"強制清理失敗: {e}")
def _basic_cleanup(self):
"""基本清理(同步)"""
debug_log("執行基本清理")
try:
if self.electron_process:
# 嘗試基本的管道關閉
for pipe_name in ["stdout", "stderr", "stdin"]:
pipe = getattr(self.electron_process, pipe_name, None)
if pipe and hasattr(pipe, "close"):
try:
pipe.close()
except Exception:
pass # 忽略錯誤
self.electron_process = None
except Exception as e:
debug_log(f"基本清理失敗: {e}")
async def _create_package_json(self):
"""創建 package.json 文件"""
package_config = {
"name": "mcp-feedback-enhanced-desktop",
"version": "2.3.0",
"description": "MCP Feedback Enhanced Desktop Application",
"main": "main.js",
"scripts": {"start": "electron .", "dev": "electron . --dev"},
"dependencies": {"electron": "^28.0.0"},
"devDependencies": {"electron-builder": "^24.0.0"},
}
package_json_path = self.desktop_dir / "package.json"
with open(package_json_path, "w", encoding="utf-8") as f:
import json
json.dump(package_config, f, indent=2, ensure_ascii=False)
debug_log(f"已創建 package.json: {package_json_path}")
async def _install_dependencies(self) -> bool:
"""安裝 Node.js 依賴"""
debug_log("開始安裝 Node.js 依賴...")
try:
# 使用 npm install
install_cmd = ["npm", "install"]
process = await asyncio.create_subprocess_exec(
*install_cmd,
cwd=self.desktop_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, stderr = await process.communicate()
if process.returncode == 0:
debug_log("Node.js 依賴安裝成功")
return True
debug_log(f"依賴安裝失敗: {stderr.decode()}")
return False
except Exception as e:
debug_log(f"依賴安裝過程中出錯: {e}")
return False
async def _start_electron_process(self) -> bool:
"""啟動 Electron 進程"""
debug_log("啟動 Electron 進程...")
try:
# 構建 Electron 命令 - 使用本地安裝的 electron
import platform
if platform.system() == "Windows":
electron_path = (
self.desktop_dir / "node_modules" / ".bin" / "electron.cmd"
)
else:
electron_path = self.desktop_dir / "node_modules" / ".bin" / "electron"
if electron_path.exists():
electron_cmd = [
str(electron_path),
".",
"--port",
str(self.web_server_port or 8765),
]
else:
# 回退到 npx
electron_cmd = [
"npx",
"electron",
".",
"--port",
str(self.web_server_port or 8765),
]
debug_log(f"使用 Electron 命令: {' '.join(electron_cmd)}")
# 啟動 Electron 進程
self.electron_process = await asyncio.create_subprocess_exec(
*electron_cmd,
cwd=self.desktop_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
debug_log(f"Electron 進程已啟動PID: {self.electron_process.pid}")
# 等待一小段時間確保進程正常啟動
await asyncio.sleep(2)
# 檢查進程是否仍在運行
if self.electron_process.returncode is None:
debug_log("Electron 進程運行正常")
return True
debug_log(
f"Electron 進程異常退出,返回碼: {self.electron_process.returncode}"
)
# 讀取錯誤輸出
try:
_, stderr = await self.electron_process.communicate()
if stderr:
debug_log(f"Electron 錯誤輸出: {stderr.decode()}")
except Exception as e:
debug_log(f"讀取 Electron 錯誤輸出失敗: {e}")
return False
except Exception as e:
debug_log(f"啟動 Electron 進程失敗: {e}")
return False
def __del__(self):
"""析構函數"""
self.cleanup()
# 便利函數
async def create_electron_manager() -> ElectronManager:
"""創建 Electron 管理器實例"""
manager = ElectronManager()
# 檢查可用性
if not manager.is_electron_available():
debug_log("Electron 環境不可用,建議使用 Web 模式")
return manager

View File

@ -1,306 +0,0 @@
#!/usr/bin/env node
/**
* Electron 主進程
* ===============
*
* 此文件是 MCP Feedback Enhanced 桌面應用的主進程入口點
* 負責創建和管理 BrowserWindow以及與現有 Web UI 的整合
*
* 主要功能
* - 創建和管理應用視窗
* - 載入本地 Web 服務器內容
* - 處理應用生命週期事件
* - 提供桌面應用特有的功能
*
* 作者: Augment Agent
* 版本: 2.3.0
*/
const { app, BrowserWindow, ipcMain, shell } = require('electron');
const path = require('path');
const os = require('os');
// 應用配置
const APP_CONFIG = {
name: 'MCP Feedback Enhanced',
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
defaultPort: 8765
};
// 全局變數
let mainWindow = null;
let webServerPort = APP_CONFIG.defaultPort;
/**
* Electron 應用類
*/
class ElectronApp {
constructor() {
this.mainWindow = null;
this.webServerPort = APP_CONFIG.defaultPort;
this.isDevMode = process.argv.includes('--dev');
this.setupEventHandlers();
this.parseCommandLineArgs();
}
/**
* 解析命令行參數
*/
parseCommandLineArgs() {
const args = process.argv;
const portIndex = args.indexOf('--port');
if (portIndex !== -1 && portIndex + 1 < args.length) {
const port = parseInt(args[portIndex + 1]);
if (!isNaN(port) && port > 0 && port < 65536) {
this.webServerPort = port;
console.log(`使用指定端口: ${port}`);
}
}
}
/**
* 設置事件處理器
*/
setupEventHandlers() {
// 應用準備就緒
app.whenReady().then(() => {
this.createWindow();
this.setupIpcHandlers();
});
// 所有視窗關閉
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// 應用激活macOS
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
this.createWindow();
}
});
// 處理證書錯誤(開發模式)
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (this.isDevMode && url.startsWith('https://localhost')) {
event.preventDefault();
callback(true);
} else {
callback(false);
}
});
}
/**
* 創建主視窗
*/
async createWindow() {
console.log('創建主視窗...');
console.log(`視窗配置: ${APP_CONFIG.width}x${APP_CONFIG.height}`);
this.mainWindow = new BrowserWindow({
width: APP_CONFIG.width,
height: APP_CONFIG.height,
minWidth: APP_CONFIG.minWidth,
minHeight: APP_CONFIG.minHeight,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
webSecurity: !this.isDevMode
},
icon: this.getAppIcon(),
title: APP_CONFIG.name,
show: false, // 先隱藏,等載入完成後顯示
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
alwaysOnTop: false, // 不總是置頂,但確保可見
center: true, // 居中顯示
resizable: true
});
// 載入 Web UI
await this.loadWebUI();
// 視窗準備顯示
this.mainWindow.once('ready-to-show', () => {
console.log('視窗準備顯示,正在顯示視窗...');
this.mainWindow.show();
this.mainWindow.focus(); // 確保視窗獲得焦點
if (this.isDevMode) {
this.mainWindow.webContents.openDevTools();
}
});
// 備用顯示機制:如果 ready-to-show 沒有觸發,強制顯示
setTimeout(() => {
if (this.mainWindow && !this.mainWindow.isVisible()) {
console.log('備用顯示機制:強制顯示視窗');
this.mainWindow.show();
this.mainWindow.focus();
}
}, 3000); // 3秒後強制顯示
// 處理視窗關閉
this.mainWindow.on('closed', () => {
this.mainWindow = null;
});
// 處理外部連結
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
console.log('主視窗創建完成');
}
/**
* 載入 Web UI
*/
async loadWebUI() {
const webUrl = `http://localhost:${this.webServerPort}`;
console.log(`載入 Web UI: ${webUrl}`);
try {
await this.mainWindow.loadURL(webUrl);
console.log('Web UI 載入成功');
} catch (error) {
console.error('載入 Web UI 失敗:', error);
// 載入錯誤頁面
const errorHtml = this.createErrorPage(error);
await this.mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(errorHtml)}`);
// 確保錯誤頁面也能顯示視窗
console.log('載入錯誤頁面,強制顯示視窗');
this.mainWindow.show();
this.mainWindow.focus();
}
}
/**
* 創建錯誤頁面
*/
createErrorPage(error) {
return `
<!DOCTYPE html>
<html>
<head>
<title>連接錯誤 - ${APP_CONFIG.name}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
margin: 0;
padding: 40px;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.error-container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
max-width: 500px;
}
h1 { color: #e74c3c; margin-bottom: 20px; }
p { color: #666; line-height: 1.6; }
.retry-btn {
background: #3498db;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
margin-top: 20px;
}
.retry-btn:hover { background: #2980b9; }
</style>
</head>
<body>
<div class="error-container">
<h1>無法連接到 Web 服務器</h1>
<p>桌面應用無法連接到本地 Web 服務器端口 ${this.webServerPort}</p>
<p>請確保 MCP 服務器正在運行</p>
<p><strong>錯誤詳情</strong> ${error.message}</p>
<button class="retry-btn" onclick="location.reload()">重試</button>
</div>
</body>
</html>
`;
}
/**
* 獲取應用圖標
*/
getAppIcon() {
const iconPath = path.join(__dirname, 'assets');
if (process.platform === 'win32') {
return path.join(iconPath, 'icon.ico');
} else if (process.platform === 'darwin') {
return path.join(iconPath, 'icon.icns');
} else {
return path.join(iconPath, 'icon.png');
}
}
/**
* 設置 IPC 處理器
*/
setupIpcHandlers() {
// 視窗控制
ipcMain.handle('window-minimize', () => {
if (this.mainWindow) {
this.mainWindow.minimize();
}
});
ipcMain.handle('window-maximize', () => {
if (this.mainWindow) {
if (this.mainWindow.isMaximized()) {
this.mainWindow.unmaximize();
} else {
this.mainWindow.maximize();
}
}
});
ipcMain.handle('window-close', () => {
if (this.mainWindow) {
this.mainWindow.close();
}
});
// 獲取系統資訊
ipcMain.handle('get-system-info', () => {
return {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
electronVersion: process.versions.electron,
chromeVersion: process.versions.chrome
};
});
console.log('IPC 處理器設置完成');
}
}
// 創建應用實例
const electronApp = new ElectronApp();
// 導出供外部使用
module.exports = electronApp;

View File

@ -1,169 +0,0 @@
{
"name": "mcp-feedback-enhanced-desktop",
"version": "2.3.0",
"description": "MCP Feedback Enhanced Desktop Application - Electron-based desktop interface for AI feedback collection",
"main": "main.js",
"author": {
"name": "Augment Agent",
"email": "minidora0702@gmail.com"
},
"license": "MIT",
"homepage": "https://github.com/minidoracat/mcp-feedback-enhanced",
"repository": {
"type": "git",
"url": "https://github.com/minidoracat/mcp-feedback-enhanced.git"
},
"keywords": [
"electron",
"mcp",
"feedback",
"ai",
"desktop",
"cross-platform"
],
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"scripts": {
"start": "electron .",
"dev": "electron . --dev",
"build": "electron-builder",
"build:win": "electron-builder --windows",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux",
"dist": "npm run build",
"pack": "electron-builder --dir",
"postinstall": "electron-builder install-app-deps",
"clean": "rimraf dist node_modules",
"lint": "eslint *.js",
"test": "echo \"桌面應用測試功能待實現\" && exit 0"
},
"dependencies": {
"electron": "^28.0.0"
},
"devDependencies": {
"electron-builder": "^24.0.0",
"rimraf": "^5.0.0",
"eslint": "^8.0.0"
},
"build": {
"appId": "com.minidoracat.mcp-feedback-enhanced",
"productName": "MCP Feedback Enhanced",
"directories": {
"output": "dist",
"buildResources": "assets"
},
"files": [
"main.js",
"preload.js",
"assets/**/*",
"node_modules/**/*"
],
"extraResources": [
{
"from": "../",
"to": "app",
"filter": [
"**/*",
"!desktop/node_modules",
"!desktop/dist",
"!**/*.pyc",
"!**/__pycache__"
]
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "assets/icon.ico",
"requestedExecutionLevel": "asInvoker"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "assets/icon.icns",
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
}
],
"icon": "assets/icon.png",
"category": "Development",
"desktop": {
"StartupNotify": "true",
"Encoding": "UTF-8",
"MimeType": "x-scheme-handler/mcp-feedback"
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "MCP Feedback Enhanced"
},
"dmg": {
"title": "MCP Feedback Enhanced",
"icon": "assets/icon.icns",
"background": "assets/dmg-background.png",
"contents": [
{
"x": 130,
"y": 220,
"type": "file"
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": 540,
"height": 380
}
},
"publish": {
"provider": "github",
"owner": "minidoracat",
"repo": "mcp-feedback-enhanced"
}
},
"electronDownload": {
"mirror": "https://npmmirror.com/mirrors/electron/"
}
}

View File

@ -1,204 +0,0 @@
/**
* Electron 預載腳本
* ==================
*
* 此腳本在渲染進程中運行但在網頁內容載入之前執行
* 它提供了安全的方式讓網頁與主進程通信同時保持安全性
*
* 主要功能
* - 提供安全的 IPC 通信接口
* - 擴展現有的 WebSocket 管理器
* - 添加桌面應用特有的 API
* - 標記桌面環境
*
* 作者: Augment Agent
* 版本: 2.3.0
*/
const { contextBridge, ipcRenderer } = require('electron');
/**
* 桌面 API 接口
* 通過 contextBridge 安全地暴露給渲染進程
*/
const desktopAPI = {
// 環境標識
isDesktop: true,
platform: process.platform,
// 視窗控制
window: {
minimize: () => ipcRenderer.invoke('window-minimize'),
maximize: () => ipcRenderer.invoke('window-maximize'),
close: () => ipcRenderer.invoke('window-close')
},
// 系統資訊
system: {
getInfo: () => ipcRenderer.invoke('get-system-info'),
platform: process.platform,
arch: process.arch
},
// 事件監聽
events: {
onSessionUpdate: (callback) => {
const wrappedCallback = (event, ...args) => callback(...args);
ipcRenderer.on('session-updated', wrappedCallback);
// 返回清理函數
return () => {
ipcRenderer.removeListener('session-updated', wrappedCallback);
};
},
onFeedbackRequest: (callback) => {
const wrappedCallback = (event, ...args) => callback(...args);
ipcRenderer.on('feedback-request', wrappedCallback);
return () => {
ipcRenderer.removeListener('feedback-request', wrappedCallback);
};
}
},
// 回饋處理
feedback: {
send: (data) => ipcRenderer.invoke('send-feedback', data),
cancel: () => ipcRenderer.invoke('cancel-feedback')
},
// 開發者工具
dev: {
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
reload: () => ipcRenderer.invoke('reload-window')
}
};
/**
* 擴展現有的 Web UI 功能
*/
const webUIExtensions = {
// 檢測桌面環境
isDesktopMode: () => true,
// 獲取桌面特有的配置
getDesktopConfig: () => ({
windowControls: true,
nativeMenus: process.platform === 'darwin',
customTitleBar: process.platform !== 'darwin'
}),
// 桌面通知
showNotification: (title, body, options = {}) => {
if ('Notification' in window && Notification.permission === 'granted') {
return new Notification(title, { body, ...options });
}
return null;
},
// 請求通知權限
requestNotificationPermission: async () => {
if ('Notification' in window) {
return await Notification.requestPermission();
}
return 'denied';
}
};
/**
* 日誌工具
*/
const logger = {
debug: (...args) => {
if (process.env.NODE_ENV === 'development') {
console.log('[Desktop Debug]', ...args);
}
},
info: (...args) => console.log('[Desktop Info]', ...args),
warn: (...args) => console.warn('[Desktop Warn]', ...args),
error: (...args) => console.error('[Desktop Error]', ...args)
};
// 暴露 API 到渲染進程
try {
// 主要的桌面 API
contextBridge.exposeInMainWorld('electronAPI', desktopAPI);
// Web UI 擴展
contextBridge.exposeInMainWorld('desktopExtensions', webUIExtensions);
// 日誌工具
contextBridge.exposeInMainWorld('desktopLogger', logger);
// 標記桌面環境(向後兼容)
contextBridge.exposeInMainWorld('MCP_DESKTOP_MODE', true);
logger.info('桌面 API 已成功暴露到渲染進程');
} catch (error) {
console.error('暴露桌面 API 失敗:', error);
}
/**
* DOM 載入完成後的初始化
*/
window.addEventListener('DOMContentLoaded', () => {
logger.debug('DOM 載入完成,開始桌面環境初始化');
// 添加桌面環境樣式類
document.body.classList.add('desktop-mode');
document.body.classList.add(`platform-${process.platform}`);
// 設置桌面環境變數
document.documentElement.style.setProperty('--is-desktop', '1');
// 如果是 macOS添加特殊樣式
if (process.platform === 'darwin') {
document.body.classList.add('macos-titlebar');
}
// 監聽鍵盤快捷鍵
document.addEventListener('keydown', (event) => {
// Ctrl/Cmd + R: 重新載入
if ((event.ctrlKey || event.metaKey) && event.key === 'r') {
if (process.env.NODE_ENV === 'development') {
event.preventDefault();
location.reload();
}
}
// F12: 開發者工具
if (event.key === 'F12' && process.env.NODE_ENV === 'development') {
event.preventDefault();
desktopAPI.dev.openDevTools();
}
// Escape: 最小化視窗
if (event.key === 'Escape' && event.ctrlKey) {
event.preventDefault();
desktopAPI.window.minimize();
}
});
logger.debug('桌面環境初始化完成');
});
/**
* 錯誤處理
*/
window.addEventListener('error', (event) => {
logger.error('渲染進程錯誤:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
});
window.addEventListener('unhandledrejection', (event) => {
logger.error('未處理的 Promise 拒絕:', event.reason);
});
logger.info('預載腳本載入完成');

View File

@ -28,7 +28,6 @@ import io
import json
import os
import sys
from enum import Enum
from typing import Annotated, Any
from fastmcp import FastMCP
@ -114,35 +113,6 @@ SSH_ENV_VARS = ["SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY"]
REMOTE_ENV_VARS = ["REMOTE_CONTAINERS", "CODESPACES"]
# ===== 回饋模式枚舉 =====
class FeedbackMode(Enum):
"""回饋模式枚舉"""
WEB = "web"
DESKTOP = "desktop"
AUTO = "auto"
def get_feedback_mode() -> FeedbackMode:
"""
從環境變數獲取回饋模式
環境變數 MCP_FEEDBACK_MODE 可設置為
- 'web': 強制使用 Web 模式
- 'desktop': 強制使用桌面模式
- 'auto': 自動檢測預設
Returns:
FeedbackMode: 回饋模式
"""
mode = os.environ.get("MCP_FEEDBACK_MODE", "auto").lower()
try:
return FeedbackMode(mode)
except ValueError:
debug_log(f"無效的 MCP_FEEDBACK_MODE 值: {mode},使用預設值 'auto'")
return FeedbackMode.AUTO
# 初始化 MCP 服務器
from . import __version__
@ -500,18 +470,10 @@ async def interactive_feedback(
project_directory = os.getcwd()
project_directory = os.path.abspath(project_directory)
# 根據模式選擇啟動方式
mode = get_feedback_mode()
debug_log(f"回饋模式: {mode.value}")
# 使用 Web 模式
debug_log("回饋模式: web")
if mode == FeedbackMode.DESKTOP:
result = await launch_desktop_feedback_ui(
project_directory, summary, timeout
)
elif mode == FeedbackMode.WEB:
result = await launch_web_feedback_ui(project_directory, summary, timeout)
else: # AUTO
result = await launch_auto_feedback_ui(project_directory, summary, timeout)
result = await launch_web_feedback_ui(project_directory, summary, timeout)
# 處理取消情況
if not result:
@ -603,70 +565,6 @@ async def launch_web_feedback_ui(project_dir: str, summary: str, timeout: int) -
}
async def launch_desktop_feedback_ui(
project_dir: str, summary: str, timeout: int
) -> dict:
"""
啟動桌面應用收集回饋
Args:
project_dir: 專案目錄路徑
summary: AI 工作摘要
timeout: 超時時間
Returns:
dict: 收集到的回饋資料
"""
debug_log(f"啟動桌面應用介面,超時時間: {timeout}")
try:
# 嘗試導入桌面模組
from .desktop import launch_desktop_app
return await launch_desktop_app(project_dir, summary, timeout)
except ImportError as e:
debug_log(f"桌面模組未安裝或不可用,回退到 Web 模式: {e}")
# 回退到 Web 模式
return await launch_web_feedback_ui(project_dir, summary, timeout)
except Exception as e:
debug_log(f"桌面應用啟動失敗,回退到 Web 模式: {e}")
# 回退到 Web 模式
return await launch_web_feedback_ui(project_dir, summary, timeout)
async def launch_auto_feedback_ui(project_dir: str, summary: str, timeout: int) -> dict:
"""
自動檢測環境並選擇合適的回饋介面
Args:
project_dir: 專案目錄路徑
summary: AI 工作摘要
timeout: 超時時間
Returns:
dict: 收集到的回饋資料
"""
debug_log("自動檢測環境以選擇回饋介面")
# 檢測是否為遠程環境
if is_remote_environment():
debug_log("檢測到遠程環境,使用 Web 模式")
return await launch_web_feedback_ui(project_dir, summary, timeout)
# 本地環境:嘗試桌面模式,失敗則回退到 Web 模式
try:
from .desktop import is_desktop_available
if is_desktop_available():
debug_log("檢測到桌面環境可用,使用桌面模式")
return await launch_desktop_feedback_ui(project_dir, summary, timeout)
except ImportError:
debug_log("桌面模組不可用")
debug_log("使用 Web 模式作為預設選擇")
return await launch_web_feedback_ui(project_dir, summary, timeout)
@mcp.tool()
def get_system_info() -> str:
"""

View File

@ -124,14 +124,12 @@ class WebUIManager:
self._init_basic_components()
debug_log(f"WebUIManager 基本初始化完成,將在 {self.host}:{self.port} 啟動")
debug_log(f"回饋模式: {self.mode}")
debug_log("回饋模式: web")
def _init_basic_components(self):
"""同步初始化基本組件"""
# 基本組件初始化(必須同步)
self.i18n = get_i18n_manager()
self.mode = self._detect_feedback_mode()
self.desktop_manager: Any = None
# 設置靜態文件和模板(必須同步)
self._setup_static_files()
@ -152,11 +150,7 @@ class WebUIManager:
# 創建並行任務
tasks = []
# 任務1桌面管理器初始化
if self.mode == "desktop":
tasks.append(self._init_desktop_manager_async())
# 任務2I18N 預載入(如果需要)
# 任務I18N 預載入(如果需要)
tasks.append(self._preload_i18n_async())
# 並行執行所有任務
@ -174,51 +168,6 @@ class WebUIManager:
elapsed = time.time() - start_time
debug_log(f"並行初始化完成,耗時: {elapsed:.2f}")
def _detect_feedback_mode(self) -> str:
"""檢測回饋模式"""
mode = os.environ.get("MCP_FEEDBACK_MODE", "auto").lower()
if mode in ["web", "desktop", "auto"]:
return mode
debug_log(f"無效的 MCP_FEEDBACK_MODE 值: {mode},使用預設值 'auto'")
return "auto"
def _init_desktop_manager(self):
"""初始化桌面管理器(如果可用)- 同步版本"""
try:
# 嘗試導入桌面模組
from ..desktop import ElectronManager
self.desktop_manager = ElectronManager()
debug_log("桌面管理器初始化成功")
except ImportError:
debug_log("桌面模組不可用,將在需要時回退到 Web 模式")
self.desktop_manager = None
except Exception as e:
debug_log(f"桌面管理器初始化失敗: {e}")
self.desktop_manager = None
async def _init_desktop_manager_async(self):
"""異步初始化桌面管理器"""
def init_desktop():
try:
from ..desktop import ElectronManager
manager = ElectronManager()
debug_log("桌面管理器異步初始化成功")
return manager
except ImportError:
debug_log("桌面模組不可用,將在需要時回退到 Web 模式")
return None
except Exception as e:
debug_log(f"桌面管理器異步初始化失敗: {e}")
return None
# 在線程池中執行同步初始化
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as executor:
self.desktop_manager = await loop.run_in_executor(executor, init_desktop)
async def _preload_i18n_async(self):
"""異步預載入 I18N 資源"""
@ -237,20 +186,6 @@ class WebUIManager:
with concurrent.futures.ThreadPoolExecutor() as executor:
await loop.run_in_executor(executor, preload_i18n)
def should_use_desktop_mode(self) -> bool:
"""判斷是否應該使用桌面模式"""
if self.mode == "web":
return False
if self.mode == "desktop":
return self.desktop_manager is not None
# auto
# 自動模式:檢測環境
from ..server import is_remote_environment
if is_remote_environment():
return False
return self.desktop_manager is not None
def _setup_compression_middleware(self):
"""設置壓縮和緩存中間件"""
# 獲取壓縮管理器