mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
✨ 桌面應用啟動速度優化
This commit is contained in:
parent
d5ba76e248
commit
85ff83f037
@ -15,8 +15,10 @@
|
||||
版本: 2.3.0
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from ..debug import web_debug_log as debug_log
|
||||
@ -64,7 +66,7 @@ def is_desktop_available() -> bool:
|
||||
|
||||
async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> dict:
|
||||
"""
|
||||
啟動桌面應用收集回饋
|
||||
啟動桌面應用收集回饋(優化版本,支援並行啟動)
|
||||
|
||||
Args:
|
||||
project_dir: 專案目錄路徑
|
||||
@ -74,61 +76,120 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
|
||||
Returns:
|
||||
dict: 收集到的回饋資料
|
||||
"""
|
||||
debug_log("啟動桌面應用...")
|
||||
debug_log("啟動桌面應用(並行優化版本)...")
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# 創建 Electron 管理器
|
||||
from .electron_manager import ElectronManager
|
||||
# 並行任務1:創建 Electron 管理器和依賴檢查
|
||||
async def init_electron_manager():
|
||||
from .electron_manager import ElectronManager
|
||||
|
||||
manager = ElectronManager()
|
||||
manager = ElectronManager()
|
||||
# 預先檢查依賴(如果實現了異步版本)
|
||||
if hasattr(manager, "ensure_dependencies_async"):
|
||||
await manager.ensure_dependencies_async()
|
||||
return manager
|
||||
|
||||
# 首先啟動 Web 服務器(桌面應用需要載入 Web UI)
|
||||
from ..web import get_web_ui_manager
|
||||
# 並行任務2:初始化 Web 管理器和會話
|
||||
async def init_web_manager():
|
||||
from ..web import get_web_ui_manager
|
||||
|
||||
web_manager = get_web_ui_manager()
|
||||
web_manager = get_web_ui_manager()
|
||||
|
||||
# 創建會話
|
||||
web_manager.create_session(project_dir, summary)
|
||||
session = web_manager.get_current_session()
|
||||
# 確保異步初始化完成
|
||||
if hasattr(web_manager, "_init_async_components"):
|
||||
await web_manager._init_async_components()
|
||||
|
||||
if not session:
|
||||
raise RuntimeError("無法創建回饋會話")
|
||||
# 創建會話
|
||||
web_manager.create_session(project_dir, summary)
|
||||
session = web_manager.get_current_session()
|
||||
|
||||
# 啟動 Web 服務器(如果尚未啟動)
|
||||
if not web_manager.server_thread or not web_manager.server_thread.is_alive():
|
||||
debug_log("啟動 Web 服務器...")
|
||||
web_manager.start_server()
|
||||
if not session:
|
||||
raise RuntimeError("無法創建回饋會話")
|
||||
|
||||
# 等待 Web 服務器完全啟動
|
||||
import time
|
||||
return web_manager, session
|
||||
|
||||
debug_log("等待 Web 服務器啟動...")
|
||||
time.sleep(5) # 增加等待時間
|
||||
# 並行執行初始化任務
|
||||
debug_log("並行執行初始化任務...")
|
||||
init_results = await asyncio.gather(
|
||||
init_electron_manager(), init_web_manager(), return_exceptions=True
|
||||
)
|
||||
|
||||
# 驗證 Web 服務器是否正常運行
|
||||
if web_manager.server_thread and web_manager.server_thread.is_alive():
|
||||
debug_log(f"✅ Web 服務器成功啟動在端口: {web_manager.port}")
|
||||
# 檢查初始化結果
|
||||
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 服務器啟動失敗")
|
||||
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(web_manager.port)
|
||||
debug_log(f"桌面應用將連接到: http://localhost:{web_manager.port}")
|
||||
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)
|
||||
desktop_success = await manager.launch_desktop_app(summary, project_dir) # type: ignore[union-attr]
|
||||
|
||||
if desktop_success:
|
||||
debug_log("桌面應用啟動成功,等待用戶回饋...")
|
||||
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
|
||||
return result # type: ignore[no-any-return]
|
||||
finally:
|
||||
# 確保 Electron 進程被正確清理
|
||||
debug_log("清理 Electron 進程...")
|
||||
await manager.cleanup_async()
|
||||
if not isinstance(manager, Exception):
|
||||
await manager.cleanup_async() # type: ignore[union-attr]
|
||||
debug_log("Electron 進程清理完成")
|
||||
else:
|
||||
debug_log("桌面應用啟動失敗,回退到 Web 模式")
|
||||
@ -142,8 +203,8 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
|
||||
debug_log("回退到 Web 模式")
|
||||
# 確保清理 Electron 進程
|
||||
try:
|
||||
if "manager" in locals():
|
||||
await manager.cleanup_async()
|
||||
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}")
|
||||
|
||||
@ -153,6 +214,41 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
|
||||
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 管理器 - 預留接口"""
|
||||
|
||||
|
@ -163,6 +163,10 @@ class ElectronManager:
|
||||
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:
|
||||
|
@ -7,6 +7,7 @@ Web UI 主要管理類
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
@ -96,25 +97,64 @@ class WebUIManager:
|
||||
|
||||
self.server_thread: threading.Thread | None = None
|
||||
self.server_process = None
|
||||
self.i18n = get_i18n_manager()
|
||||
|
||||
# 添加模式檢測支援
|
||||
# 初始化標記,用於追蹤異步初始化狀態
|
||||
self._initialization_complete = False
|
||||
self._initialization_lock = threading.Lock()
|
||||
|
||||
# 同步初始化基本組件
|
||||
self._init_basic_components()
|
||||
|
||||
debug_log(f"WebUIManager 基本初始化完成,將在 {self.host}:{self.port} 啟動")
|
||||
debug_log(f"回饋模式: {self.mode}")
|
||||
|
||||
def _init_basic_components(self):
|
||||
"""同步初始化基本組件"""
|
||||
# 基本組件初始化(必須同步)
|
||||
self.i18n = get_i18n_manager()
|
||||
self.mode = self._detect_feedback_mode()
|
||||
self.desktop_manager: Any = None
|
||||
|
||||
# 如果是桌面模式,嘗試初始化桌面管理器
|
||||
if self.mode == "desktop":
|
||||
self._init_desktop_manager()
|
||||
|
||||
# 設置靜態文件和模板
|
||||
# 設置靜態文件和模板(必須同步)
|
||||
self._setup_static_files()
|
||||
self._setup_templates()
|
||||
|
||||
# 設置路由
|
||||
# 設置路由(必須同步)
|
||||
setup_routes(self)
|
||||
|
||||
debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動")
|
||||
debug_log(f"回饋模式: {self.mode}")
|
||||
async def _init_async_components(self):
|
||||
"""異步初始化組件(並行執行)"""
|
||||
with self._initialization_lock:
|
||||
if self._initialization_complete:
|
||||
return
|
||||
|
||||
debug_log("開始並行初始化組件...")
|
||||
start_time = time.time()
|
||||
|
||||
# 創建並行任務
|
||||
tasks = []
|
||||
|
||||
# 任務1:桌面管理器初始化
|
||||
if self.mode == "desktop":
|
||||
tasks.append(self._init_desktop_manager_async())
|
||||
|
||||
# 任務2:I18N 預載入(如果需要)
|
||||
tasks.append(self._preload_i18n_async())
|
||||
|
||||
# 並行執行所有任務
|
||||
if tasks:
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# 檢查結果
|
||||
for i, result in enumerate(results):
|
||||
if isinstance(result, Exception):
|
||||
debug_log(f"並行初始化任務 {i} 失敗: {result}")
|
||||
|
||||
with self._initialization_lock:
|
||||
self._initialization_complete = True
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
debug_log(f"並行初始化完成,耗時: {elapsed:.2f}秒")
|
||||
|
||||
def _detect_feedback_mode(self) -> str:
|
||||
"""檢測回饋模式"""
|
||||
@ -125,7 +165,7 @@ class WebUIManager:
|
||||
return "auto"
|
||||
|
||||
def _init_desktop_manager(self):
|
||||
"""初始化桌面管理器(如果可用)"""
|
||||
"""初始化桌面管理器(如果可用)- 同步版本"""
|
||||
try:
|
||||
# 嘗試導入桌面模組
|
||||
from ..desktop import ElectronManager
|
||||
@ -139,6 +179,46 @@ class WebUIManager:
|
||||
debug_log(f"桌面管理器初始化失敗: {e}")
|
||||
self.desktop_manager = None
|
||||
|
||||
async def _init_desktop_manager_async(self):
|
||||
"""異步初始化桌面管理器"""
|
||||
|
||||
def init_desktop():
|
||||
try:
|
||||
from ..desktop import ElectronManager
|
||||
|
||||
manager = ElectronManager()
|
||||
debug_log("桌面管理器異步初始化成功")
|
||||
return manager
|
||||
except ImportError:
|
||||
debug_log("桌面模組不可用,將在需要時回退到 Web 模式")
|
||||
return None
|
||||
except Exception as e:
|
||||
debug_log(f"桌面管理器異步初始化失敗: {e}")
|
||||
return None
|
||||
|
||||
# 在線程池中執行同步初始化
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
self.desktop_manager = await loop.run_in_executor(executor, init_desktop)
|
||||
|
||||
async def _preload_i18n_async(self):
|
||||
"""異步預載入 I18N 資源"""
|
||||
|
||||
def preload_i18n():
|
||||
try:
|
||||
# 觸發翻譯載入(如果尚未載入)
|
||||
self.i18n.get_supported_languages()
|
||||
debug_log("I18N 資源預載入完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
debug_log(f"I18N 資源預載入失敗: {e}")
|
||||
return False
|
||||
|
||||
# 在線程池中執行
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
await loop.run_in_executor(executor, preload_i18n)
|
||||
|
||||
def should_use_desktop_mode(self) -> bool:
|
||||
"""判斷是否應該使用桌面模式"""
|
||||
if self.mode == "web":
|
||||
@ -415,7 +495,7 @@ class WebUIManager:
|
||||
debug_log(f"廣播消息失敗: {e}")
|
||||
|
||||
def start_server(self):
|
||||
"""啟動 Web 伺服器"""
|
||||
"""啟動 Web 伺服器(優化版本,支援並行初始化)"""
|
||||
|
||||
def run_server_with_retry():
|
||||
max_retries = 5
|
||||
@ -435,8 +515,20 @@ class WebUIManager:
|
||||
access_log=False,
|
||||
)
|
||||
|
||||
server = uvicorn.Server(config)
|
||||
asyncio.run(server.serve())
|
||||
server_instance = uvicorn.Server(config)
|
||||
|
||||
# 創建事件循環並啟動服務器
|
||||
async def serve_with_async_init(server=server_instance):
|
||||
# 在服務器啟動的同時進行異步初始化
|
||||
server_task = asyncio.create_task(server.serve())
|
||||
init_task = asyncio.create_task(self._init_async_components())
|
||||
|
||||
# 等待兩個任務完成
|
||||
await asyncio.gather(
|
||||
server_task, init_task, return_exceptions=True
|
||||
)
|
||||
|
||||
asyncio.run(serve_with_async_init())
|
||||
break
|
||||
|
||||
except OSError as e:
|
||||
|
Loading…
x
Reference in New Issue
Block a user