🔥 移除 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*/
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 # Logs
*.log *.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] [project]
name = "mcp-feedback-enhanced" name = "mcp-feedback-enhanced"
version = "2.3.0" 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" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
authors = [ authors = [
{ name = "Minidoracat", email = "minidora0702@gmail.com" } { 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 = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Intended Audience :: Developers", "Intended Audience :: Developers",
@ -34,10 +34,6 @@ dev = [
"pytest>=7.0.0", "pytest>=7.0.0",
"pytest-asyncio>=0.21.0", "pytest-asyncio>=0.21.0",
] ]
desktop = [
# Node.js 環境需要系統安裝,不是 Python 套件
# 請確保系統已安裝 Node.js >= 16.0.0
]
[project.urls] [project.urls]
Homepage = "https://github.com/Minidoracat/mcp-feedback-enhanced" Homepage = "https://github.com/Minidoracat/mcp-feedback-enhanced"
@ -99,7 +95,7 @@ exclude = [
"buck-out", "buck-out",
"build", "build",
"dist", "dist",
"node_modules",
"venv", "venv",
"*.egg-info", "*.egg-info",
".trunk", ".trunk",
@ -268,7 +264,7 @@ exclude = [
".venv/", ".venv/",
"venv/", "venv/",
".trunk/", ".trunk/",
"node_modules/",
".mypy_cache/", ".mypy_cache/",
] ]

View File

@ -47,15 +47,6 @@ def main():
test_parser.add_argument( test_parser.add_argument(
"--web", action="store_true", help="測試 Web UI (自動持續運行)" "--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( test_parser.add_argument(
"--timeout", type=int, default=60, help="測試超時時間 (秒)" "--timeout", type=int, default=60, help="測試超時時間 (秒)"
) )
@ -109,28 +100,10 @@ def run_tests(args):
success = test_web_ui_simple() success = test_web_ui_simple()
if not success: if not success:
sys.exit(1) 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: else:
print("❌ 測試功能已簡化") print("❌ 測試功能已簡化")
print("💡 可用的測試選項:") print("💡 可用的測試選項:")
print(" --web 測試 Web UI") print(" --web 測試 Web UI")
print(" --desktop 測試桌面應用")
print(" --full 完整整合測試")
print(" --electron-only 僅測試 Electron 環境")
print("💡 對於開發者:使用 'uv run pytest' 執行完整測試") print("💡 對於開發者:使用 'uv run pytest' 執行完整測試")
sys.exit(1) sys.exit(1)
@ -212,68 +185,6 @@ def test_web_ui_simple():
os.environ.pop("MCP_WEB_PORT", None) 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): async def wait_for_process(process):
"""等待進程結束""" """等待進程結束"""
try: try:
@ -295,162 +206,6 @@ async def wait_for_process(process):
print(f"等待進程時出錯: {e}") 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(): def show_version():
"""顯示版本資訊""" """顯示版本資訊"""
from . import __author__, __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 json
import os import os
import sys import sys
from enum import Enum
from typing import Annotated, Any from typing import Annotated, Any
from fastmcp import FastMCP from fastmcp import FastMCP
@ -114,35 +113,6 @@ SSH_ENV_VARS = ["SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY"]
REMOTE_ENV_VARS = ["REMOTE_CONTAINERS", "CODESPACES"] 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 服務器 # 初始化 MCP 服務器
from . import __version__ from . import __version__
@ -500,18 +470,10 @@ async def interactive_feedback(
project_directory = os.getcwd() project_directory = os.getcwd()
project_directory = os.path.abspath(project_directory) project_directory = os.path.abspath(project_directory)
# 根據模式選擇啟動方式 # 使用 Web 模式
mode = get_feedback_mode() debug_log("回饋模式: web")
debug_log(f"回饋模式: {mode.value}")
if mode == FeedbackMode.DESKTOP: result = await launch_web_feedback_ui(project_directory, summary, timeout)
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)
# 處理取消情況 # 處理取消情況
if not result: 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() @mcp.tool()
def get_system_info() -> str: def get_system_info() -> str:
""" """

View File

@ -124,14 +124,12 @@ class WebUIManager:
self._init_basic_components() self._init_basic_components()
debug_log(f"WebUIManager 基本初始化完成,將在 {self.host}:{self.port} 啟動") debug_log(f"WebUIManager 基本初始化完成,將在 {self.host}:{self.port} 啟動")
debug_log(f"回饋模式: {self.mode}") debug_log("回饋模式: web")
def _init_basic_components(self): def _init_basic_components(self):
"""同步初始化基本組件""" """同步初始化基本組件"""
# 基本組件初始化(必須同步) # 基本組件初始化(必須同步)
self.i18n = get_i18n_manager() self.i18n = get_i18n_manager()
self.mode = self._detect_feedback_mode()
self.desktop_manager: Any = None
# 設置靜態文件和模板(必須同步) # 設置靜態文件和模板(必須同步)
self._setup_static_files() self._setup_static_files()
@ -152,11 +150,7 @@ class WebUIManager:
# 創建並行任務 # 創建並行任務
tasks = [] tasks = []
# 任務1桌面管理器初始化 # 任務I18N 預載入(如果需要)
if self.mode == "desktop":
tasks.append(self._init_desktop_manager_async())
# 任務2I18N 預載入(如果需要)
tasks.append(self._preload_i18n_async()) tasks.append(self._preload_i18n_async())
# 並行執行所有任務 # 並行執行所有任務
@ -174,51 +168,6 @@ class WebUIManager:
elapsed = time.time() - start_time elapsed = time.time() - start_time
debug_log(f"並行初始化完成,耗時: {elapsed:.2f}") 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): async def _preload_i18n_async(self):
"""異步預載入 I18N 資源""" """異步預載入 I18N 資源"""
@ -237,20 +186,6 @@ class WebUIManager:
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
await loop.run_in_executor(executor, preload_i18n) 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): def _setup_compression_middleware(self):
"""設置壓縮和緩存中間件""" """設置壓縮和緩存中間件"""
# 獲取壓縮管理器 # 獲取壓縮管理器