mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
✨ 新增 desktop 模式
This commit is contained in:
parent
3ed676cf29
commit
a2d4180cf6
51
.gitignore
vendored
51
.gitignore
vendored
@ -10,13 +10,62 @@ wheels/
|
||||
.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
|
||||
|
||||
#Others
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Linux
|
||||
*~
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Others
|
||||
.cursor/rules/
|
||||
uv.lock
|
||||
.mcp_feedback_settings.json
|
||||
test_reports/
|
||||
|
||||
# Temporary test files
|
||||
test_*.py
|
||||
|
||||
# User configuration files
|
||||
ui_settings.json
|
||||
.config/
|
154
electron-builder.json
Normal file
154
electron-builder.json
Normal file
@ -0,0 +1,154 @@
|
||||
{
|
||||
"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"
|
||||
}
|
@ -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."
|
||||
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."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
authors = [
|
||||
{ name = "Minidoracat", email = "minidora0702@gmail.com" }
|
||||
]
|
||||
keywords = ["mcp", "ai", "feedback", "web-ui", "interactive", "development"]
|
||||
keywords = ["mcp", "ai", "feedback", "web-ui", "interactive", "development", "electron", "desktop", "cross-platform"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
@ -34,6 +34,9 @@ dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
]
|
||||
desktop = [
|
||||
"nodejs>=16.0.0", # Node.js 環境(需要系統安裝)
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/Minidoracat/mcp-feedback-enhanced"
|
||||
|
@ -14,6 +14,19 @@ MCP Interactive Feedback Enhanced - 主程式入口
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import asyncio
|
||||
import warnings
|
||||
|
||||
# 抑制 Windows 上的 asyncio ResourceWarning
|
||||
if sys.platform == 'win32':
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning, message=".*unclosed transport.*")
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning, message=".*unclosed.*")
|
||||
|
||||
# 設置 asyncio 事件循環策略以減少警告
|
||||
try:
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def main():
|
||||
"""主程式入口點"""
|
||||
@ -29,6 +42,9 @@ def main():
|
||||
# 測試命令
|
||||
test_parser = subparsers.add_parser('test', help='執行測試')
|
||||
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='測試超時時間 (秒)')
|
||||
|
||||
# 版本命令
|
||||
@ -59,14 +75,37 @@ def run_tests(args):
|
||||
# 啟用調試模式以顯示測試過程
|
||||
os.environ["MCP_DEBUG"] = "true"
|
||||
|
||||
# 在 Windows 上抑制 asyncio 警告
|
||||
if sys.platform == 'win32':
|
||||
os.environ["PYTHONWARNINGS"] = "ignore::ResourceWarning"
|
||||
|
||||
if args.web:
|
||||
print("🧪 執行 Web UI 測試...")
|
||||
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("💡 對於用戶:使用 'test --web' 測試 Web UI")
|
||||
print("💡 可用的測試選項:")
|
||||
print(" --web 測試 Web UI")
|
||||
print(" --desktop 測試桌面應用")
|
||||
print(" --full 完整整合測試")
|
||||
print(" --electron-only 僅測試 Electron 環境")
|
||||
print("💡 對於開發者:使用 'uv run pytest' 執行完整測試")
|
||||
sys.exit(1)
|
||||
|
||||
@ -135,6 +174,215 @@ def test_web_ui_simple():
|
||||
return False
|
||||
|
||||
|
||||
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_FEEDBACK_MODE'] = 'desktop'
|
||||
|
||||
print("🔧 創建 Electron 管理器...")
|
||||
from .desktop.electron_manager import ElectronManager
|
||||
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
|
||||
|
||||
|
||||
async def wait_for_process(process):
|
||||
"""等待進程結束"""
|
||||
try:
|
||||
# 等待進程自然結束
|
||||
await process.wait()
|
||||
|
||||
# 確保管道正確關閉
|
||||
try:
|
||||
if hasattr(process, 'stdout') and process.stdout:
|
||||
process.stdout.close()
|
||||
if hasattr(process, 'stderr') and process.stderr:
|
||||
process.stderr.close()
|
||||
if hasattr(process, 'stdin') and process.stdin:
|
||||
process.stdin.close()
|
||||
except Exception as close_error:
|
||||
print(f"關閉進程管道時出錯: {close_error}")
|
||||
|
||||
except Exception as 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)
|
||||
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 基本功能...")
|
||||
from .web.main import WebUIManager
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
manager = WebUIManager(host="127.0.0.1", port=8766) # 使用不同端口避免衝突
|
||||
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
|
||||
|
||||
|
||||
def show_version():
|
||||
"""顯示版本資訊"""
|
||||
from . import __version__, __author__
|
||||
|
168
src/mcp_feedback_enhanced/desktop/__init__.py
Normal file
168
src/mcp_feedback_enhanced/desktop/__init__.py
Normal file
@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
桌面應用模組
|
||||
===========
|
||||
|
||||
此模組提供 Electron 桌面應用整合功能,實現零前端改動的桌面化方案。
|
||||
|
||||
主要功能:
|
||||
- Electron 進程管理
|
||||
- 與現有 Web UI 的無縫整合
|
||||
- 跨平台桌面應用支援
|
||||
- 環境變數控制
|
||||
|
||||
作者: Augment Agent
|
||||
版本: 2.3.0
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
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)
|
||||
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("啟動桌面應用...")
|
||||
|
||||
try:
|
||||
# 創建 Electron 管理器
|
||||
from .electron_manager import ElectronManager
|
||||
manager = ElectronManager()
|
||||
|
||||
# 首先啟動 Web 服務器(桌面應用需要載入 Web UI)
|
||||
from ..web import get_web_ui_manager
|
||||
web_manager = get_web_ui_manager()
|
||||
|
||||
# 創建會話
|
||||
session_id = web_manager.create_session(project_dir, summary)
|
||||
session = web_manager.get_current_session()
|
||||
|
||||
if not session:
|
||||
raise RuntimeError("無法創建回饋會話")
|
||||
|
||||
# 啟動 Web 服務器(如果尚未啟動)
|
||||
if not web_manager.server_thread or not web_manager.server_thread.is_alive():
|
||||
debug_log("啟動 Web 服務器...")
|
||||
web_manager.start_server()
|
||||
|
||||
# 等待 Web 服務器完全啟動
|
||||
import time
|
||||
debug_log("等待 Web 服務器啟動...")
|
||||
time.sleep(5) # 增加等待時間
|
||||
|
||||
# 驗證 Web 服務器是否正常運行
|
||||
if web_manager.server_thread and web_manager.server_thread.is_alive():
|
||||
debug_log(f"✅ Web 服務器成功啟動在端口: {web_manager.port}")
|
||||
else:
|
||||
raise RuntimeError(f"Web 服務器啟動失敗")
|
||||
|
||||
# 設置 Web 服務器端口
|
||||
manager.set_web_server_port(web_manager.port)
|
||||
debug_log(f"桌面應用將連接到: http://localhost:{web_manager.port}")
|
||||
|
||||
# 啟動桌面應用
|
||||
desktop_success = await manager.launch_desktop_app(summary, project_dir)
|
||||
|
||||
if desktop_success:
|
||||
debug_log("桌面應用啟動成功,等待用戶回饋...")
|
||||
# 等待用戶回饋
|
||||
result = await session.wait_for_feedback(timeout)
|
||||
debug_log("收到桌面應用用戶回饋")
|
||||
return result
|
||||
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 模式")
|
||||
# 回退到 Web 模式
|
||||
from ..web import launch_web_feedback_ui
|
||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||
|
||||
|
||||
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__ = [
|
||||
"is_desktop_available",
|
||||
"launch_desktop_app",
|
||||
"ElectronManager"
|
||||
]
|
39
src/mcp_feedback_enhanced/desktop/assets/README.md
Normal file
39
src/mcp_feedback_enhanced/desktop/assets/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# 應用圖標資源
|
||||
|
||||
此目錄包含桌面應用的圖標資源文件。
|
||||
|
||||
## 需要的圖標文件
|
||||
|
||||
### 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 會自動處理圖標的打包
|
||||
- 確保圖標文件的版權合規性
|
@ -0,0 +1,28 @@
|
||||
# 圖標佔位符
|
||||
|
||||
此文件標記圖標資源的位置。在實際部署中,需要替換為真實的圖標文件:
|
||||
|
||||
## 需要的圖標文件:
|
||||
|
||||
### Windows
|
||||
- icon.ico (多尺寸:16x16, 32x32, 48x48, 256x256)
|
||||
|
||||
### macOS
|
||||
- icon.icns (多尺寸:16x16 到 1024x1024)
|
||||
|
||||
### Linux
|
||||
- icon.png (建議 512x512)
|
||||
|
||||
## 臨時解決方案
|
||||
|
||||
在開發階段,可以:
|
||||
1. 使用 Electron 預設圖標
|
||||
2. 從現有 Web UI 的 favicon 轉換
|
||||
3. 使用線上圖標生成器創建基本圖標
|
||||
|
||||
## 圖標設計建議
|
||||
|
||||
- 簡潔明了,體現 MCP 和回饋收集的概念
|
||||
- 在小尺寸下仍然清晰可辨
|
||||
- 符合各平台的設計規範
|
||||
- 使用一致的色彩方案
|
309
src/mcp_feedback_enhanced/desktop/electron_manager.py
Normal file
309
src/mcp_feedback_enhanced/desktop/electron_manager.py
Normal file
@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Electron 管理器
|
||||
==============
|
||||
|
||||
此模組負責管理 Electron 進程的生命週期,包括:
|
||||
- Electron 應用啟動和停止
|
||||
- 與 Web 服務器的整合
|
||||
- 依賴檢測和自動安裝
|
||||
- 進程管理和錯誤處理
|
||||
|
||||
此文件為階段 1 的預留實現,完整功能將在階段 2 中實現。
|
||||
|
||||
作者: Augment Agent
|
||||
版本: 2.3.0
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ..debug import web_debug_log as debug_log
|
||||
from ..utils.error_handler import ErrorHandler, ErrorType
|
||||
|
||||
|
||||
class ElectronManager:
|
||||
"""Electron 進程管理器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化 Electron 管理器"""
|
||||
self.electron_process: Optional[subprocess.Popen] = None
|
||||
self.desktop_dir = Path(__file__).parent
|
||||
self.web_server_port: Optional[int] = None
|
||||
|
||||
debug_log("ElectronManager 初始化完成")
|
||||
debug_log(f"桌面模組目錄: {self.desktop_dir}")
|
||||
|
||||
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
|
||||
else:
|
||||
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)
|
||||
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
|
||||
|
||||
def cleanup(self):
|
||||
"""清理資源"""
|
||||
if self.electron_process:
|
||||
try:
|
||||
# 檢查進程是否還在運行
|
||||
if self.electron_process.returncode is None:
|
||||
self.electron_process.terminate()
|
||||
debug_log("Electron 進程已終止")
|
||||
else:
|
||||
debug_log("Electron 進程已自然結束")
|
||||
except Exception as e:
|
||||
debug_log(f"終止 Electron 進程時出錯: {e}")
|
||||
try:
|
||||
if self.electron_process.returncode is None:
|
||||
self.electron_process.kill()
|
||||
debug_log("強制終止 Electron 進程")
|
||||
except Exception as kill_error:
|
||||
debug_log(f"強制終止 Electron 進程失敗: {kill_error}")
|
||||
finally:
|
||||
# 關閉管道以避免 ResourceWarning
|
||||
try:
|
||||
# 對於 asyncio 子進程,需要特殊處理
|
||||
if hasattr(self.electron_process, 'stdout') and self.electron_process.stdout:
|
||||
if hasattr(self.electron_process.stdout, 'close'):
|
||||
self.electron_process.stdout.close()
|
||||
if hasattr(self.electron_process, 'stderr') and self.electron_process.stderr:
|
||||
if hasattr(self.electron_process.stderr, 'close'):
|
||||
self.electron_process.stderr.close()
|
||||
if hasattr(self.electron_process, 'stdin') and self.electron_process.stdin:
|
||||
if hasattr(self.electron_process.stdin, 'close'):
|
||||
self.electron_process.stdin.close()
|
||||
except Exception:
|
||||
# 忽略管道關閉錯誤,這些通常是無害的
|
||||
pass
|
||||
|
||||
self.electron_process = None
|
||||
|
||||
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
|
||||
else:
|
||||
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
|
||||
else:
|
||||
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
|
306
src/mcp_feedback_enhanced/desktop/main.js
Normal file
306
src/mcp_feedback_enhanced/desktop/main.js
Normal file
@ -0,0 +1,306 @@
|
||||
#!/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;
|
169
src/mcp_feedback_enhanced/desktop/package.json
Normal file
169
src/mcp_feedback_enhanced/desktop/package.json
Normal file
@ -0,0 +1,169 @@
|
||||
{
|
||||
"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/"
|
||||
}
|
||||
}
|
204
src/mcp_feedback_enhanced/desktop/preload.js
Normal file
204
src/mcp_feedback_enhanced/desktop/preload.js
Normal file
@ -0,0 +1,204 @@
|
||||
/**
|
||||
* 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('預載腳本載入完成');
|
@ -31,6 +31,7 @@ import tempfile
|
||||
import asyncio
|
||||
import base64
|
||||
from typing import Annotated, List
|
||||
from enum import Enum
|
||||
import io
|
||||
|
||||
from fastmcp import FastMCP
|
||||
@ -108,6 +109,34 @@ SERVER_NAME = "互動式回饋收集 MCP"
|
||||
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__
|
||||
|
||||
@ -452,8 +481,16 @@ async def interactive_feedback(
|
||||
project_directory = os.getcwd()
|
||||
project_directory = os.path.abspath(project_directory)
|
||||
|
||||
# 使用 Web UI
|
||||
result = await launch_web_ui_with_timeout(project_directory, summary, timeout)
|
||||
# 根據模式選擇啟動方式
|
||||
mode = get_feedback_mode()
|
||||
debug_log(f"回饋模式: {mode.value}")
|
||||
|
||||
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)
|
||||
|
||||
# 處理取消情況
|
||||
if not result:
|
||||
@ -499,7 +536,7 @@ async def interactive_feedback(
|
||||
return [TextContent(type="text", text=user_error_msg)]
|
||||
|
||||
|
||||
async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: int) -> dict:
|
||||
async def launch_web_feedback_ui(project_dir: str, summary: str, timeout: int) -> dict:
|
||||
"""
|
||||
啟動 Web UI 收集回饋,支援自訂超時時間
|
||||
|
||||
@ -515,10 +552,10 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
||||
|
||||
try:
|
||||
# 使用新的 web 模組
|
||||
from .web import launch_web_feedback_ui, stop_web_ui
|
||||
from .web import launch_web_feedback_ui as web_launch, stop_web_ui
|
||||
|
||||
# 傳遞 timeout 參數給 Web UI
|
||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||
return await web_launch(project_dir, summary, timeout)
|
||||
except ImportError as e:
|
||||
# 使用統一錯誤處理
|
||||
error_id = ErrorHandler.log_error_with_context(
|
||||
@ -534,49 +571,66 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
||||
"interactive_feedback": user_error_msg,
|
||||
"images": []
|
||||
}
|
||||
except TimeoutError as e:
|
||||
debug_log(f"Web UI 超時: {e}")
|
||||
# 超時時確保停止 Web 服務器
|
||||
try:
|
||||
from .web import stop_web_ui
|
||||
stop_web_ui()
|
||||
debug_log("Web UI 服務器已因超時而停止")
|
||||
except Exception as stop_error:
|
||||
debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}")
|
||||
|
||||
return {
|
||||
"command_logs": "",
|
||||
"interactive_feedback": f"回饋收集超時({timeout}秒),介面已自動關閉。",
|
||||
"images": []
|
||||
}
|
||||
|
||||
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:
|
||||
# 使用統一錯誤處理
|
||||
error_id = ErrorHandler.log_error_with_context(
|
||||
e,
|
||||
context={"operation": "Web UI 啟動", "timeout": timeout},
|
||||
error_type=ErrorType.SYSTEM
|
||||
)
|
||||
user_error_msg = ErrorHandler.format_user_error(e, include_technical=False)
|
||||
debug_log(f"❌ Web UI 錯誤 [錯誤ID: {error_id}]: {e}")
|
||||
debug_log(f"桌面應用啟動失敗,回退到 Web 模式: {e}")
|
||||
# 回退到 Web 模式
|
||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||
|
||||
# 發生錯誤時也要停止 Web 服務器
|
||||
|
||||
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 .web import stop_web_ui
|
||||
stop_web_ui()
|
||||
debug_log("Web UI 服務器已因錯誤而停止")
|
||||
except Exception as stop_error:
|
||||
ErrorHandler.log_error_with_context(
|
||||
stop_error,
|
||||
context={"operation": "Web UI 服務器停止"},
|
||||
error_type=ErrorType.SYSTEM
|
||||
)
|
||||
debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}")
|
||||
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("桌面模組不可用")
|
||||
|
||||
return {
|
||||
"command_logs": "",
|
||||
"interactive_feedback": user_error_msg,
|
||||
"images": []
|
||||
}
|
||||
debug_log("使用 Web 模式作為預設選擇")
|
||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
@ -100,6 +100,14 @@ class WebUIManager:
|
||||
self.server_process = None
|
||||
self.i18n = get_i18n_manager()
|
||||
|
||||
# 添加模式檢測支援
|
||||
self.mode = self._detect_feedback_mode()
|
||||
self.desktop_manager = None
|
||||
|
||||
# 如果是桌面模式,嘗試初始化桌面管理器
|
||||
if self.mode == "desktop":
|
||||
self._init_desktop_manager()
|
||||
|
||||
# 設置靜態文件和模板
|
||||
self._setup_static_files()
|
||||
self._setup_templates()
|
||||
@ -108,6 +116,43 @@ class WebUIManager:
|
||||
setup_routes(self)
|
||||
|
||||
debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動")
|
||||
debug_log(f"回饋模式: {self.mode}")
|
||||
|
||||
def _detect_feedback_mode(self) -> str:
|
||||
"""檢測回饋模式"""
|
||||
mode = os.environ.get('MCP_FEEDBACK_MODE', 'auto').lower()
|
||||
if mode in ['web', 'desktop', 'auto']:
|
||||
return mode
|
||||
else:
|
||||
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
|
||||
|
||||
def should_use_desktop_mode(self) -> bool:
|
||||
"""判斷是否應該使用桌面模式"""
|
||||
if self.mode == "web":
|
||||
return False
|
||||
elif self.mode == "desktop":
|
||||
return self.desktop_manager is not None
|
||||
else: # 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):
|
||||
"""設置壓縮和緩存中間件"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user