mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
🐛 fixes #5 實際 MCP 的 timeout 參數會影響到 GUI 及 Web UI 自動關閉
This commit is contained in:
parent
e7283b3610
commit
5d09c3309a
@ -20,6 +20,6 @@
|
|||||||
重構: 模塊化設計
|
重構: 模塊化設計
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .main import feedback_ui
|
from .main import feedback_ui, feedback_ui_with_timeout
|
||||||
|
|
||||||
__all__ = ['feedback_ui']
|
__all__ = ['feedback_ui', 'feedback_ui_with_timeout']
|
@ -7,9 +7,12 @@ GUI 主要入口點
|
|||||||
提供 GUI 回饋介面的主要入口點函數。
|
提供 GUI 回饋介面的主要入口點函數。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .models import FeedbackResult
|
from .models import FeedbackResult
|
||||||
@ -51,4 +54,69 @@ def feedback_ui(project_directory: str, summary: str) -> Optional[FeedbackResult
|
|||||||
app.exec()
|
app.exec()
|
||||||
|
|
||||||
# 返回結果
|
# 返回結果
|
||||||
return window.result
|
return window.result
|
||||||
|
|
||||||
|
|
||||||
|
def feedback_ui_with_timeout(project_directory: str, summary: str, timeout: int) -> Optional[FeedbackResult]:
|
||||||
|
"""
|
||||||
|
啟動帶超時的回饋收集 GUI 介面
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_directory: 專案目錄路徑
|
||||||
|
summary: AI 工作摘要
|
||||||
|
timeout: 超時時間(秒)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[FeedbackResult]: 回饋結果,如果用戶取消或超時則返回 None
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TimeoutError: 當超時時拋出
|
||||||
|
"""
|
||||||
|
# 檢查是否已有 QApplication 實例
|
||||||
|
app = QApplication.instance()
|
||||||
|
if app is None:
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# 設定全域微軟正黑體字體
|
||||||
|
font = QFont("Microsoft JhengHei", 11) # 微軟正黑體,11pt
|
||||||
|
app.setFont(font)
|
||||||
|
|
||||||
|
# 設定字體回退順序,確保中文字體正確顯示
|
||||||
|
app.setStyleSheet("""
|
||||||
|
* {
|
||||||
|
font-family: "Microsoft JhengHei", "微軟正黑體", "Microsoft YaHei", "微软雅黑", "SimHei", "黑体", sans-serif;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 創建主窗口
|
||||||
|
window = FeedbackWindow(project_directory, summary)
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
# 創建超時計時器
|
||||||
|
timeout_timer = QTimer()
|
||||||
|
timeout_timer.setSingleShot(True)
|
||||||
|
timeout_timer.timeout.connect(lambda: _handle_timeout(window, app))
|
||||||
|
timeout_timer.start(timeout * 1000) # 轉換為毫秒
|
||||||
|
|
||||||
|
# 運行事件循環直到窗口關閉
|
||||||
|
app.exec()
|
||||||
|
|
||||||
|
# 停止計時器(如果還在運行)
|
||||||
|
timeout_timer.stop()
|
||||||
|
|
||||||
|
# 檢查是否超時
|
||||||
|
if hasattr(window, '_timeout_occurred'):
|
||||||
|
raise TimeoutError(f"回饋收集超時({timeout}秒),GUI 介面已自動關閉")
|
||||||
|
|
||||||
|
# 返回結果
|
||||||
|
return window.result
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_timeout(window: FeedbackWindow, app: QApplication) -> None:
|
||||||
|
"""處理超時事件"""
|
||||||
|
# 標記超時發生
|
||||||
|
window._timeout_occurred = True
|
||||||
|
# 強制關閉視窗
|
||||||
|
window.force_close()
|
||||||
|
# 退出應用程式
|
||||||
|
app.quit()
|
@ -447,17 +447,16 @@ class FeedbackWindow(QMainWindow):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def _cancel_feedback(self) -> None:
|
def _cancel_feedback(self) -> None:
|
||||||
"""取消回饋"""
|
"""取消回饋收集"""
|
||||||
reply = QMessageBox.question(
|
debug_log("取消回饋收集")
|
||||||
self, t('app.confirmCancel'),
|
self.result = ""
|
||||||
t('app.confirmCancelMessage'),
|
self.close()
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
|
||||||
QMessageBox.No
|
def force_close(self) -> None:
|
||||||
)
|
"""強制關閉視窗(用於超時處理)"""
|
||||||
|
debug_log("強制關閉視窗(超時)")
|
||||||
if reply == QMessageBox.Yes:
|
self.result = ""
|
||||||
self.result = None
|
self.close()
|
||||||
self.close()
|
|
||||||
|
|
||||||
def _refresh_ui_texts(self) -> None:
|
def _refresh_ui_texts(self) -> None:
|
||||||
"""刷新界面文字"""
|
"""刷新界面文字"""
|
||||||
|
@ -356,45 +356,37 @@ def process_images(images_data: List[dict]) -> List[MCPImage]:
|
|||||||
return mcp_images
|
return mcp_images
|
||||||
|
|
||||||
|
|
||||||
def launch_gui(project_dir: str, summary: str) -> dict:
|
async def launch_gui_with_timeout(project_dir: str, summary: str, timeout: int) -> dict:
|
||||||
"""
|
"""
|
||||||
啟動 Qt GUI 收集回饋
|
啟動 GUI 模式並處理超時
|
||||||
|
|
||||||
Args:
|
|
||||||
project_dir: 專案目錄路徑
|
|
||||||
summary: AI 工作摘要
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 收集到的回饋資料
|
|
||||||
"""
|
"""
|
||||||
debug_log("啟動 Qt GUI 介面")
|
debug_log(f"啟動 GUI 模式(超時:{timeout}秒)")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .gui import feedback_ui
|
from .gui import feedback_ui_with_timeout
|
||||||
result = feedback_ui(project_dir, summary)
|
|
||||||
|
|
||||||
if result is None:
|
# 直接調用帶超時的 GUI 函數
|
||||||
# 用戶取消
|
result = feedback_ui_with_timeout(project_dir, summary, timeout)
|
||||||
|
|
||||||
|
if result:
|
||||||
return {
|
return {
|
||||||
"command_logs": "",
|
"logs": f"GUI 模式回饋收集完成",
|
||||||
"interactive_feedback": "用戶取消了回饋。",
|
"interactive_feedback": result.get("interactive_feedback", ""),
|
||||||
|
"images": result.get("images", [])
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"logs": "用戶取消了回饋收集",
|
||||||
|
"interactive_feedback": "",
|
||||||
"images": []
|
"images": []
|
||||||
}
|
}
|
||||||
|
|
||||||
# 轉換鍵名以保持向後兼容
|
except TimeoutError as e:
|
||||||
return {
|
# 超時異常 - 這是預期的行為
|
||||||
"command_logs": result.get("command_logs", ""),
|
raise e
|
||||||
"interactive_feedback": result.get("interactive_feedback", ""),
|
except Exception as e:
|
||||||
"images": result.get("images", [])
|
debug_log(f"GUI 啟動失败: {e}")
|
||||||
}
|
raise Exception(f"GUI 啟動失败: {e}")
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
debug_log(f"無法導入 GUI 模組: {e}")
|
|
||||||
return {
|
|
||||||
"command_logs": "",
|
|
||||||
"interactive_feedback": f"Qt GUI 模組導入失敗: {str(e)}",
|
|
||||||
"images": []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ===== MCP 工具定義 =====
|
# ===== MCP 工具定義 =====
|
||||||
@ -462,7 +454,7 @@ async def interactive_feedback(
|
|||||||
if use_web_ui:
|
if use_web_ui:
|
||||||
result = await launch_web_ui_with_timeout(project_directory, summary, timeout)
|
result = await launch_web_ui_with_timeout(project_directory, summary, timeout)
|
||||||
else:
|
else:
|
||||||
result = launch_gui(project_directory, summary)
|
result = await launch_gui_with_timeout(project_directory, summary, timeout)
|
||||||
|
|
||||||
# 處理取消情況
|
# 處理取消情況
|
||||||
if not result:
|
if not result:
|
||||||
@ -517,8 +509,8 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
|||||||
# 使用新的 web 模組
|
# 使用新的 web 模組
|
||||||
from .web import launch_web_feedback_ui
|
from .web import launch_web_feedback_ui
|
||||||
|
|
||||||
# 直接運行 Web UI 會話
|
# 傳遞 timeout 參數給 Web UI
|
||||||
return await launch_web_feedback_ui(project_dir, summary)
|
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
debug_log(f"無法導入 Web UI 模組: {e}")
|
debug_log(f"無法導入 Web UI 模組: {e}")
|
||||||
return {
|
return {
|
||||||
@ -526,6 +518,13 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
|||||||
"interactive_feedback": f"Web UI 模組導入失敗: {str(e)}",
|
"interactive_feedback": f"Web UI 模組導入失敗: {str(e)}",
|
||||||
"images": []
|
"images": []
|
||||||
}
|
}
|
||||||
|
except TimeoutError as e:
|
||||||
|
debug_log(f"Web UI 超時: {e}")
|
||||||
|
return {
|
||||||
|
"command_logs": "",
|
||||||
|
"interactive_feedback": f"回饋收集超時({timeout}秒),介面已自動關閉。",
|
||||||
|
"images": []
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Web UI 錯誤: {e}"
|
error_msg = f"Web UI 錯誤: {e}"
|
||||||
debug_log(f"❌ {error_msg}")
|
debug_log(f"❌ {error_msg}")
|
||||||
|
@ -147,6 +147,12 @@
|
|||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"download": "Download"
|
"download": "Download"
|
||||||
},
|
},
|
||||||
|
"session": {
|
||||||
|
"timeout": "⏰ Session has timed out, interface will close automatically",
|
||||||
|
"timeoutWarning": "Session is about to timeout",
|
||||||
|
"timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.",
|
||||||
|
"closing": "Closing..."
|
||||||
|
},
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
"aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!",
|
"aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!",
|
||||||
"terminalWelcome": "Welcome to Interactive Feedback Terminal\n========================================\nProject Directory: {sessionId}\nEnter commands and press Enter or click Execute button\nSupported commands: ls, dir, pwd, cat, type, etc.\n\n$ "
|
"terminalWelcome": "Welcome to Interactive Feedback Terminal\n========================================\nProject Directory: {sessionId}\nEnter commands and press Enter or click Execute button\nSupported commands: ls, dir, pwd, cat, type, etc.\n\n$ "
|
||||||
|
@ -147,6 +147,12 @@
|
|||||||
"upload": "上传",
|
"upload": "上传",
|
||||||
"download": "下载"
|
"download": "下载"
|
||||||
},
|
},
|
||||||
|
"session": {
|
||||||
|
"timeout": "⏰ 会话已超时,界面将自动关闭",
|
||||||
|
"timeoutWarning": "会话即将超时",
|
||||||
|
"timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。",
|
||||||
|
"closing": "正在关闭..."
|
||||||
|
},
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
"aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈!",
|
"aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈!",
|
||||||
"terminalWelcome": "欢迎使用交互反馈终端\n========================================\n项目目录: {sessionId}\n输入命令后按 Enter 或点击执行按钮\n支持的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
"terminalWelcome": "欢迎使用交互反馈终端\n========================================\n项目目录: {sessionId}\n输入命令后按 Enter 或点击执行按钮\n支持的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
||||||
|
@ -147,6 +147,12 @@
|
|||||||
"upload": "上傳",
|
"upload": "上傳",
|
||||||
"download": "下載"
|
"download": "下載"
|
||||||
},
|
},
|
||||||
|
"session": {
|
||||||
|
"timeout": "⏰ 會話已超時,介面將自動關閉",
|
||||||
|
"timeoutWarning": "會話即將超時",
|
||||||
|
"timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。",
|
||||||
|
"closing": "正在關閉..."
|
||||||
|
},
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
"aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋!",
|
"aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋!",
|
||||||
"terminalWelcome": "歡迎使用互動回饋終端\n========================================\n專案目錄: {sessionId}\n輸入命令後按 Enter 或點擊執行按鈕\n支援的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
"terminalWelcome": "歡迎使用互動回饋終端\n========================================\n專案目錄: {sessionId}\n輸入命令後按 Enter 或點擊執行按鈕\n支援的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
||||||
|
@ -174,13 +174,14 @@ def get_web_ui_manager() -> WebUIManager:
|
|||||||
return _web_ui_manager
|
return _web_ui_manager
|
||||||
|
|
||||||
|
|
||||||
async def launch_web_feedback_ui(project_directory: str, summary: str) -> dict:
|
async def launch_web_feedback_ui(project_directory: str, summary: str, timeout: int = 600) -> dict:
|
||||||
"""
|
"""
|
||||||
啟動 Web 回饋介面並等待用戶回饋
|
啟動 Web 回饋介面並等待用戶回饋
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
project_directory: 專案目錄路徑
|
project_directory: 專案目錄路徑
|
||||||
summary: AI 工作摘要
|
summary: AI 工作摘要
|
||||||
|
timeout: 超時時間(秒)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: 回饋結果,包含 logs、interactive_feedback 和 images
|
dict: 回饋結果,包含 logs、interactive_feedback 和 images
|
||||||
@ -203,12 +204,19 @@ async def launch_web_feedback_ui(project_directory: str, summary: str) -> dict:
|
|||||||
manager.open_browser(feedback_url)
|
manager.open_browser(feedback_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 等待用戶回饋
|
# 等待用戶回饋,傳遞 timeout 參數
|
||||||
result = await session.wait_for_feedback()
|
result = await session.wait_for_feedback(timeout)
|
||||||
debug_log(f"收到用戶回饋,會話: {session_id}")
|
debug_log(f"收到用戶回饋,會話: {session_id}")
|
||||||
return result
|
return result
|
||||||
|
except TimeoutError:
|
||||||
|
debug_log(f"會話 {session_id} 超時")
|
||||||
|
# 資源已在 wait_for_feedback 中清理,這裡只需要記錄和重新拋出
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"會話 {session_id} 發生錯誤: {e}")
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
# 清理會話
|
# 清理會話(無論成功還是失敗)
|
||||||
manager.remove_session(session_id)
|
manager.remove_session(session_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,13 +37,14 @@ class WebFeedbackSession:
|
|||||||
self.feedback_completed = threading.Event()
|
self.feedback_completed = threading.Event()
|
||||||
self.process: Optional[subprocess.Popen] = None
|
self.process: Optional[subprocess.Popen] = None
|
||||||
self.command_logs = []
|
self.command_logs = []
|
||||||
|
self._cleanup_done = False # 防止重複清理
|
||||||
|
|
||||||
# 確保臨時目錄存在
|
# 確保臨時目錄存在
|
||||||
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
TEMP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
async def wait_for_feedback(self, timeout: int = 600) -> dict:
|
async def wait_for_feedback(self, timeout: int = 600) -> dict:
|
||||||
"""
|
"""
|
||||||
等待用戶回饋,包含圖片
|
等待用戶回饋,包含圖片,支援超時自動清理
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
timeout: 超時時間(秒)
|
timeout: 超時時間(秒)
|
||||||
@ -51,21 +52,40 @@ class WebFeedbackSession:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: 回饋結果
|
dict: 回饋結果
|
||||||
"""
|
"""
|
||||||
loop = asyncio.get_event_loop()
|
try:
|
||||||
|
# 使用比 MCP 超時稍短的時間(提前處理,避免邊界競爭)
|
||||||
def wait_in_thread():
|
# 對於短超時(<30秒),提前1秒;對於長超時,提前5秒
|
||||||
return self.feedback_completed.wait(timeout)
|
if timeout <= 30:
|
||||||
|
actual_timeout = max(timeout - 1, 5) # 短超時提前1秒,最少5秒
|
||||||
completed = await loop.run_in_executor(None, wait_in_thread)
|
else:
|
||||||
|
actual_timeout = timeout - 5 # 長超時提前5秒
|
||||||
if completed:
|
debug_log(f"會話 {self.session_id} 開始等待回饋,超時時間: {actual_timeout} 秒(原始: {timeout} 秒)")
|
||||||
return {
|
|
||||||
"logs": "\n".join(self.command_logs),
|
loop = asyncio.get_event_loop()
|
||||||
"interactive_feedback": self.feedback_result or "",
|
|
||||||
"images": self.images
|
def wait_in_thread():
|
||||||
}
|
return self.feedback_completed.wait(actual_timeout)
|
||||||
else:
|
|
||||||
raise TimeoutError("等待用戶回饋超時")
|
completed = await loop.run_in_executor(None, wait_in_thread)
|
||||||
|
|
||||||
|
if completed:
|
||||||
|
debug_log(f"會話 {self.session_id} 收到用戶回饋")
|
||||||
|
return {
|
||||||
|
"logs": "\n".join(self.command_logs),
|
||||||
|
"interactive_feedback": self.feedback_result or "",
|
||||||
|
"images": self.images
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 超時了,立即清理資源
|
||||||
|
debug_log(f"會話 {self.session_id} 在 {actual_timeout} 秒後超時,開始清理資源...")
|
||||||
|
await self._cleanup_resources_on_timeout()
|
||||||
|
raise TimeoutError(f"等待用戶回饋超時({actual_timeout}秒),介面已自動關閉")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 任何異常都要確保清理資源
|
||||||
|
debug_log(f"會話 {self.session_id} 發生異常: {e}")
|
||||||
|
await self._cleanup_resources_on_timeout()
|
||||||
|
raise
|
||||||
|
|
||||||
async def submit_feedback(self, feedback: str, images: List[dict]):
|
async def submit_feedback(self, feedback: str, images: List[dict]):
|
||||||
"""
|
"""
|
||||||
@ -224,8 +244,66 @@ class WebFeedbackSession:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def _cleanup_resources_on_timeout(self):
|
||||||
|
"""超時時清理所有資源"""
|
||||||
|
if self._cleanup_done:
|
||||||
|
return # 避免重複清理
|
||||||
|
|
||||||
|
self._cleanup_done = True
|
||||||
|
debug_log(f"開始清理會話 {self.session_id} 的資源...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 關閉 WebSocket 連接
|
||||||
|
if self.websocket:
|
||||||
|
try:
|
||||||
|
# 先通知前端超時
|
||||||
|
await self.websocket.send_json({
|
||||||
|
"type": "session_timeout",
|
||||||
|
"message": "會話已超時,介面將自動關閉"
|
||||||
|
})
|
||||||
|
await asyncio.sleep(0.1) # 給前端一點時間處理消息
|
||||||
|
await self.websocket.close()
|
||||||
|
debug_log(f"會話 {self.session_id} WebSocket 已關閉")
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"關閉 WebSocket 時發生錯誤: {e}")
|
||||||
|
finally:
|
||||||
|
self.websocket = None
|
||||||
|
|
||||||
|
# 2. 終止正在運行的命令進程
|
||||||
|
if self.process:
|
||||||
|
try:
|
||||||
|
self.process.terminate()
|
||||||
|
try:
|
||||||
|
self.process.wait(timeout=3)
|
||||||
|
debug_log(f"會話 {self.session_id} 命令進程已正常終止")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.process.kill()
|
||||||
|
debug_log(f"會話 {self.session_id} 命令進程已強制終止")
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"終止命令進程時發生錯誤: {e}")
|
||||||
|
finally:
|
||||||
|
self.process = None
|
||||||
|
|
||||||
|
# 3. 設置完成事件(防止其他地方還在等待)
|
||||||
|
self.feedback_completed.set()
|
||||||
|
|
||||||
|
# 4. 清理臨時數據
|
||||||
|
self.command_logs.clear()
|
||||||
|
self.images.clear()
|
||||||
|
|
||||||
|
debug_log(f"會話 {self.session_id} 資源清理完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"清理會話 {self.session_id} 資源時發生錯誤: {e}")
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""清理會話資源"""
|
"""同步清理會話資源(保持向後兼容)"""
|
||||||
|
if self._cleanup_done:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._cleanup_done = True
|
||||||
|
debug_log(f"同步清理會話 {self.session_id} 資源...")
|
||||||
|
|
||||||
if self.process:
|
if self.process:
|
||||||
try:
|
try:
|
||||||
self.process.terminate()
|
self.process.terminate()
|
||||||
@ -235,4 +313,7 @@ class WebFeedbackSession:
|
|||||||
self.process.kill()
|
self.process.kill()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
|
# 設置完成事件
|
||||||
|
self.feedback_completed.set()
|
@ -242,6 +242,10 @@ class FeedbackApp {
|
|||||||
// 顯示成功訊息
|
// 顯示成功訊息
|
||||||
this.showSuccessMessage();
|
this.showSuccessMessage();
|
||||||
break;
|
break;
|
||||||
|
case 'session_timeout':
|
||||||
|
console.log('會話超時:', data.message);
|
||||||
|
this.handleSessionTimeout(data.message);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('未知的 WebSocket 消息:', data);
|
console.log('未知的 WebSocket 消息:', data);
|
||||||
}
|
}
|
||||||
@ -254,6 +258,54 @@ class FeedbackApp {
|
|||||||
this.showMessage(successMessage, 'success');
|
this.showMessage(successMessage, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSessionTimeout(message) {
|
||||||
|
console.log('處理會話超時:', message);
|
||||||
|
|
||||||
|
// 顯示超時訊息
|
||||||
|
const timeoutMessage = message || (window.i18nManager ?
|
||||||
|
window.i18nManager.t('session.timeout', '⏰ 會話已超時,介面將自動關閉') :
|
||||||
|
'⏰ 會話已超時,介面將自動關閉');
|
||||||
|
|
||||||
|
this.showMessage(timeoutMessage, 'warning');
|
||||||
|
|
||||||
|
// 禁用所有互動元素
|
||||||
|
this.disableAllInputs();
|
||||||
|
|
||||||
|
// 3秒後自動關閉頁面
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
window.close();
|
||||||
|
} catch (e) {
|
||||||
|
// 如果無法關閉視窗(可能因為安全限制),重新載入頁面
|
||||||
|
console.log('無法關閉視窗,重新載入頁面');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
disableAllInputs() {
|
||||||
|
// 禁用所有輸入元素
|
||||||
|
const inputs = document.querySelectorAll('input, textarea, button');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.disabled = true;
|
||||||
|
input.style.opacity = '0.5';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 特別處理提交和取消按鈕
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const cancelBtn = document.getElementById('cancelBtn');
|
||||||
|
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.textContent = '⏰ 已超時';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.textContent = '關閉中...';
|
||||||
|
cancelBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateConnectionStatus(connected) {
|
updateConnectionStatus(connected) {
|
||||||
// 更新連接狀態指示器
|
// 更新連接狀態指示器
|
||||||
const elements = document.querySelectorAll('.connection-indicator');
|
const elements = document.querySelectorAll('.connection-indicator');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user