🔧 增強調試功能,新增 debug_log 函數以輸出調試訊息,並在多個模組中替換原有的 print 語句。更新伺服器初始化以確保編碼正確性,並在 Windows 環境下設置 stdio 為二進制模式。

This commit is contained in:
Minidoracat 2025-05-31 05:20:46 +08:00
parent 86e55b89f6
commit aa4cb3a136
5 changed files with 564 additions and 483 deletions

View File

@ -41,12 +41,8 @@ def main():
elif args.command == 'version': elif args.command == 'version':
show_version() show_version()
elif args.command == 'server': elif args.command == 'server':
# 明確指定伺服器命令
print("🚀 啟動 Interactive Feedback MCP Enhanced 伺服器...")
run_server() run_server()
elif args.command is None: elif args.command is None:
# 沒有指定命令,啟動伺服器(保持向後兼容)
print("🚀 啟動 Interactive Feedback MCP Enhanced 伺服器...")
run_server() run_server()
else: else:
# 不應該到達這裡 # 不應該到達這裡

View File

@ -30,6 +30,29 @@ from PySide6.QtWidgets import (
from PySide6.QtCore import Qt, Signal, QTimer from PySide6.QtCore import Qt, Signal, QTimer
from PySide6.QtGui import QFont, QPixmap, QDragEnterEvent, QDropEvent, QKeySequence, QShortcut from PySide6.QtGui import QFont, QPixmap, QDragEnterEvent, QDropEvent, QKeySequence, QShortcut
# ===== 調試日誌函數 =====
def debug_log(message: str) -> None:
"""輸出調試訊息到標準錯誤,避免污染標準輸出"""
# 只在啟用調試模式時才輸出,避免干擾 MCP 通信
if not os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on"):
return
try:
# 確保消息是字符串類型
if not isinstance(message, str):
message = str(message)
# 安全地輸出到 stderr處理編碼問題
try:
print(f"[GUI_DEBUG] {message}", file=sys.stderr, flush=True)
except UnicodeEncodeError:
# 如果遇到編碼問題,使用 ASCII 安全模式
safe_message = message.encode('ascii', errors='replace').decode('ascii')
print(f"[GUI_DEBUG] {safe_message}", file=sys.stderr, flush=True)
except Exception:
# 最後的備用方案:靜默失敗,不影響主程序
pass
# ===== 型別定義 ===== # ===== 型別定義 =====
class FeedbackResult(TypedDict): class FeedbackResult(TypedDict):
"""回饋結果的型別定義""" """回饋結果的型別定義"""
@ -265,9 +288,8 @@ class ImageUploadWidget(QWidget):
new_height = int(image.height() * scale) new_height = int(image.height() * scale)
# 縮放圖片 # 縮放圖片
from PySide6.QtCore import Qt
image = image.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image = image.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
print(f"[DEBUG] 圖片已縮放至: {new_width}x{new_height}") debug_log(f"圖片已縮放至: {new_width}x{new_height}")
# 使用較低的質量保存以減小文件大小 # 使用較低的質量保存以減小文件大小
quality = 70 # 降低質量以減小文件大小 quality = 70 # 降低質量以減小文件大小
@ -275,7 +297,7 @@ class ImageUploadWidget(QWidget):
# 檢查保存後的文件大小 # 檢查保存後的文件大小
if temp_file.exists(): if temp_file.exists():
file_size = temp_file.stat().st_size file_size = temp_file.stat().st_size
print(f"[DEBUG] 剪貼板圖片保存成功: {temp_file}, 大小: {file_size} bytes") debug_log(f"剪貼板圖片保存成功: {temp_file}, 大小: {file_size} bytes")
# 檢查文件大小是否超過限制 # 檢查文件大小是否超過限制
if file_size > 1 * 1024 * 1024: # 1MB 限制 if file_size > 1 * 1024 * 1024: # 1MB 限制
@ -319,34 +341,34 @@ class ImageUploadWidget(QWidget):
if os.path.exists(file_path): if os.path.exists(file_path):
os.remove(file_path) os.remove(file_path)
temp_files_cleaned += 1 temp_files_cleaned += 1
print(f"[DEBUG] 已刪除臨時文件: {file_path}") debug_log(f"已刪除臨時文件: {file_path}")
except Exception as e: except Exception as e:
print(f"[DEBUG] 刪除臨時文件失敗: {e}") debug_log(f"刪除臨時文件失敗: {e}")
# 清除內存中的圖片數據 # 清除內存中的圖片數據
self.images.clear() self.images.clear()
self._refresh_preview() self._refresh_preview()
self._update_status() self._update_status()
self.images_changed.emit() self.images_changed.emit()
print(f"[DEBUG] 已清除所有圖片,包括 {temp_files_cleaned} 個臨時文件") debug_log(f"已清除所有圖片,包括 {temp_files_cleaned} 個臨時文件")
def _add_images(self, file_paths: List[str]) -> None: def _add_images(self, file_paths: List[str]) -> None:
"""添加圖片""" """添加圖片"""
added_count = 0 added_count = 0
for file_path in file_paths: for file_path in file_paths:
try: try:
print(f"[DEBUG] 嘗試添加圖片: {file_path}") debug_log(f"嘗試添加圖片: {file_path}")
if not os.path.exists(file_path): if not os.path.exists(file_path):
print(f"[DEBUG] 文件不存在: {file_path}") debug_log(f"文件不存在: {file_path}")
continue continue
if not self._is_image_file(file_path): if not self._is_image_file(file_path):
print(f"[DEBUG] 不是圖片文件: {file_path}") debug_log(f"不是圖片文件: {file_path}")
continue continue
file_size = os.path.getsize(file_path) file_size = os.path.getsize(file_path)
print(f"[DEBUG] 文件大小: {file_size} bytes") debug_log(f"文件大小: {file_size} bytes")
# 更嚴格的大小限制1MB # 更嚴格的大小限制1MB
if file_size > 1 * 1024 * 1024: if file_size > 1 * 1024 * 1024:
@ -364,10 +386,10 @@ class ImageUploadWidget(QWidget):
# 讀取圖片原始二進制數據 # 讀取圖片原始二進制數據
with open(file_path, 'rb') as f: with open(file_path, 'rb') as f:
raw_data = f.read() raw_data = f.read()
print(f"[DEBUG] 讀取原始數據大小: {len(raw_data)} bytes") debug_log(f"讀取原始數據大小: {len(raw_data)} bytes")
if len(raw_data) == 0: if len(raw_data) == 0:
print(f"[DEBUG] 讀取的數據為空!") debug_log(f"讀取的數據為空!")
continue continue
# 再次檢查內存中的數據大小 # 再次檢查內存中的數據大小
@ -386,14 +408,14 @@ class ImageUploadWidget(QWidget):
"size": file_size "size": file_size
} }
added_count += 1 added_count += 1
print(f"[DEBUG] 圖片添加成功: {os.path.basename(file_path)}, 數據大小: {len(raw_data)} bytes") debug_log(f"圖片添加成功: {os.path.basename(file_path)}, 數據大小: {len(raw_data)} bytes")
except Exception as e: except Exception as e:
print(f"[DEBUG] 添加圖片失敗: {e}") debug_log(f"添加圖片失敗: {e}")
QMessageBox.warning(self, "錯誤", f"無法載入圖片 {os.path.basename(file_path)}:\n{str(e)}") QMessageBox.warning(self, "錯誤", f"無法載入圖片 {os.path.basename(file_path)}:\n{str(e)}")
print(f"[DEBUG] 共添加 {added_count} 張圖片") debug_log(f"共添加 {added_count} 張圖片")
print(f"[DEBUG] 當前總共有 {len(self.images)} 張圖片") debug_log(f"當前總共有 {len(self.images)} 張圖片")
if added_count > 0: if added_count > 0:
self._refresh_preview() self._refresh_preview()
self._update_status() self._update_status()
@ -432,16 +454,16 @@ class ImageUploadWidget(QWidget):
try: try:
if os.path.exists(file_path): if os.path.exists(file_path):
os.remove(file_path) os.remove(file_path)
print(f"[DEBUG] 已刪除臨時文件: {file_path}") debug_log(f"已刪除臨時文件: {file_path}")
except Exception as e: except Exception as e:
print(f"[DEBUG] 刪除臨時文件失敗: {e}") debug_log(f"刪除臨時文件失敗: {e}")
# 從內存中移除圖片數據 # 從內存中移除圖片數據
del self.images[image_id] del self.images[image_id]
self._refresh_preview() self._refresh_preview()
self._update_status() self._update_status()
self.images_changed.emit() self.images_changed.emit()
print(f"[DEBUG] 已移除圖片: {image_info['name']}") debug_log(f"已移除圖片: {image_info['name']}")
def _update_status(self) -> None: def _update_status(self) -> None:
"""更新狀態標籤""" """更新狀態標籤"""
@ -464,9 +486,9 @@ class ImageUploadWidget(QWidget):
self.status_label.setText(f"已選擇 {count} 張圖片 (總計 {size_str})") self.status_label.setText(f"已選擇 {count} 張圖片 (總計 {size_str})")
# 詳細調試信息 # 詳細調試信息
print(f"[DEBUG] === 圖片狀態更新 ===") debug_log(f"=== 圖片狀態更新 ===")
print(f"[DEBUG] 圖片數量: {count}") debug_log(f"圖片數量: {count}")
print(f"[DEBUG] 總大小: {total_size} bytes ({size_str})") debug_log(f"總大小: {total_size} bytes ({size_str})")
for i, (image_id, img) in enumerate(self.images.items(), 1): for i, (image_id, img) in enumerate(self.images.items(), 1):
data_size = len(img["data"]) if isinstance(img["data"], bytes) else 0 data_size = len(img["data"]) if isinstance(img["data"], bytes) else 0
# 智能顯示每張圖片的大小 # 智能顯示每張圖片的大小
@ -476,8 +498,8 @@ class ImageUploadWidget(QWidget):
data_str = f"{data_size/1024:.1f} KB" data_str = f"{data_size/1024:.1f} KB"
else: else:
data_str = f"{data_size/(1024*1024):.1f} MB" data_str = f"{data_size/(1024*1024):.1f} MB"
print(f"[DEBUG] 圖片 {i}: {img['name']} - 數據大小: {data_str}") debug_log(f"圖片 {i}: {img['name']} - 數據大小: {data_str}")
print(f"[DEBUG] ==================") debug_log(f"==================")
def get_images_data(self) -> List[dict]: def get_images_data(self) -> List[dict]:
"""獲取圖片數據""" """獲取圖片數據"""
@ -552,11 +574,11 @@ class ImageUploadWidget(QWidget):
temp_file.unlink() temp_file.unlink()
cleaned_count += 1 cleaned_count += 1
except Exception as e: except Exception as e:
print(f"[DEBUG] 清理舊臨時文件失敗: {e}") debug_log(f"清理舊臨時文件失敗: {e}")
if cleaned_count > 0: if cleaned_count > 0:
print(f"[DEBUG] 清理了 {cleaned_count} 個舊的臨時文件") debug_log(f"清理了 {cleaned_count} 個舊的臨時文件")
except Exception as e: except Exception as e:
print(f"[DEBUG] 臨時文件清理過程出錯: {e}") debug_log(f"臨時文件清理過程出錯: {e}")
# ===== 主要回饋介面 ===== # ===== 主要回饋介面 =====
@ -886,11 +908,11 @@ class FeedbackWindow(QMainWindow):
if os.path.exists(file_path): if os.path.exists(file_path):
os.remove(file_path) os.remove(file_path)
temp_files_cleaned += 1 temp_files_cleaned += 1
print(f"[DEBUG] 關閉時清理臨時文件: {file_path}") debug_log(f"關閉時清理臨時文件: {file_path}")
except Exception as e: except Exception as e:
print(f"[DEBUG] 關閉時清理臨時文件失敗: {e}") debug_log(f"關閉時清理臨時文件失敗: {e}")
if temp_files_cleaned > 0: if temp_files_cleaned > 0:
print(f"[DEBUG] 視窗關閉時清理了 {temp_files_cleaned} 個臨時文件") debug_log(f"視窗關閉時清理了 {temp_files_cleaned} 個臨時文件")
event.accept() event.accept()
@ -933,6 +955,6 @@ if __name__ == "__main__":
# 測試用的主程式 # 測試用的主程式
result = feedback_ui(".", "測試摘要") result = feedback_ui(".", "測試摘要")
if result: if result:
print("收到回饋:", result) debug_log(f"收到回饋: {result}")
else: else:
print("用戶取消了回饋") debug_log("用戶取消了回饋")

View File

@ -26,6 +26,59 @@ from mcp.server.fastmcp.utilities.types import Image as MCPImage
from mcp.types import TextContent from mcp.types import TextContent
from pydantic import Field from pydantic import Field
# ===== 編碼初始化 =====
def init_encoding():
"""初始化編碼設置,確保正確處理中文字符"""
try:
# Windows 特殊處理
if sys.platform == 'win32':
import msvcrt
# 設置為二進制模式
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
# 重新包裝為 UTF-8 文本流,並禁用緩衝
sys.stdin = io.TextIOWrapper(
sys.stdin.detach(),
encoding='utf-8',
errors='replace',
newline=None
)
sys.stdout = io.TextIOWrapper(
sys.stdout.detach(),
encoding='utf-8',
errors='replace',
newline='',
write_through=True # 關鍵:禁用寫入緩衝
)
else:
# 非 Windows 系統的標準設置
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
if hasattr(sys.stdin, 'reconfigure'):
sys.stdin.reconfigure(encoding='utf-8', errors='replace')
# 設置 stderr 編碼(用於調試訊息)
if hasattr(sys.stderr, 'reconfigure'):
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
return True
except Exception as e:
# 如果編碼設置失敗,嘗試基本設置
try:
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
if hasattr(sys.stdin, 'reconfigure'):
sys.stdin.reconfigure(encoding='utf-8', errors='replace')
if hasattr(sys.stderr, 'reconfigure'):
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
except:
pass
return False
# 初始化編碼(在導入時就執行)
_encoding_initialized = init_encoding()
# ===== 常數定義 ===== # ===== 常數定義 =====
SERVER_NAME = "互動式回饋收集 MCP" SERVER_NAME = "互動式回饋收集 MCP"
SSH_ENV_VARS = ['SSH_CONNECTION', 'SSH_CLIENT', 'SSH_TTY'] SSH_ENV_VARS = ['SSH_CONNECTION', 'SSH_CLIENT', 'SSH_TTY']
@ -39,7 +92,25 @@ mcp = FastMCP(SERVER_NAME, version=__version__)
# ===== 工具函數 ===== # ===== 工具函數 =====
def debug_log(message: str) -> None: def debug_log(message: str) -> None:
"""輸出調試訊息到標準錯誤,避免污染標準輸出""" """輸出調試訊息到標準錯誤,避免污染標準輸出"""
print(f"[DEBUG] {message}", file=sys.stderr) # 只在啟用調試模式時才輸出,避免干擾 MCP 通信
if not os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on"):
return
try:
# 確保消息是字符串類型
if not isinstance(message, str):
message = str(message)
# 安全地輸出到 stderr處理編碼問題
try:
print(f"[DEBUG] {message}", file=sys.stderr, flush=True)
except UnicodeEncodeError:
# 如果遇到編碼問題,使用 ASCII 安全模式
safe_message = message.encode('ascii', errors='replace').decode('ascii')
print(f"[DEBUG] {safe_message}", file=sys.stderr, flush=True)
except Exception:
# 最後的備用方案:靜默失敗,不影響主程序
pass
def is_remote_environment() -> bool: def is_remote_environment() -> bool:
@ -331,6 +402,10 @@ async def interactive_feedback(
3. 上傳圖片作為回饋 3. 上傳圖片作為回饋
4. 查看 AI 的工作摘要 4. 查看 AI 的工作摘要
調試模式
- 設置環境變數 MCP_DEBUG=true 可啟用詳細調試輸出
- 生產環境建議關閉調試模式以避免輸出干擾
Args: Args:
project_directory: 專案目錄路徑 project_directory: 專案目錄路徑
summary: AI 工作完成的摘要說明 summary: AI 工作完成的摘要說明
@ -453,10 +528,11 @@ async def _run_web_ui_session(project_dir: str, summary: str, timeout: int) -> d
session_url = f"http://{manager.host}:{manager.port}/session/{session_id}" session_url = f"http://{manager.host}:{manager.port}/session/{session_id}"
debug_log(f"Web UI 已啟動: {session_url}") debug_log(f"Web UI 已啟動: {session_url}")
try: # 注意:不能使用 print() 污染 stdout會破壞 MCP 通信
print(f"Web UI 已啟動: {session_url}") # try:
except UnicodeEncodeError: # print(f"Web UI 已啟動: {session_url}")
print(f"Web UI launched: {session_url}") # except UnicodeEncodeError:
# print(f"Web UI launched: {session_url}")
# 開啟瀏覽器 # 開啟瀏覽器
manager.open_browser(session_url) manager.open_browser(session_url)
@ -474,10 +550,11 @@ async def _run_web_ui_session(project_dir: str, summary: str, timeout: int) -> d
except TimeoutError: except TimeoutError:
timeout_msg = f"等待用戶回饋超時({timeout} 秒)" timeout_msg = f"等待用戶回饋超時({timeout} 秒)"
debug_log(f"{timeout_msg}") debug_log(f"{timeout_msg}")
try: # 注意:不能使用 print() 污染 stdout會破壞 MCP 通信
print(f"等待用戶回饋超時({timeout} 秒)") # try:
except UnicodeEncodeError: # print(f"等待用戶回饋超時({timeout} 秒)")
print(f"Feedback timeout ({timeout} seconds)") # except UnicodeEncodeError:
# print(f"Feedback timeout ({timeout} seconds)")
return { return {
"logs": "", "logs": "",
"interactive_feedback": f"回饋超時({timeout} 秒)", "interactive_feedback": f"回饋超時({timeout} 秒)",
@ -486,10 +563,11 @@ async def _run_web_ui_session(project_dir: str, summary: str, timeout: int) -> d
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}")
try: # 注意:不能使用 print() 污染 stdout會破壞 MCP 通信
print(f"Web UI 錯誤: {e}") # try:
except UnicodeEncodeError: # print(f"Web UI 錯誤: {e}")
print(f"Web UI error: {e}") # except UnicodeEncodeError:
# print(f"Web UI error: {e}")
return { return {
"logs": "", "logs": "",
"interactive_feedback": f"錯誤: {str(e)}", "interactive_feedback": f"錯誤: {str(e)}",
@ -532,60 +610,34 @@ def get_system_info() -> str:
# ===== 主程式入口 ===== # ===== 主程式入口 =====
def main(): def main():
"""主要入口點,用於套件執行""" """主要入口點,用於套件執行"""
debug_log("🚀 啟動互動式回饋收集 MCP 服務器") # 檢查是否啟用調試模式
debug_log(f" 遠端環境: {is_remote_environment()}") debug_enabled = os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on")
debug_log(f" GUI 可用: {can_use_gui()}")
debug_log(f" 建議介面: {'Web UI' if is_remote_environment() or not can_use_gui() else 'Qt GUI'}")
debug_log(" 等待來自 AI 助手的調用...")
# Windows 特殊處理:設置 stdio 為二進制模式,避免編碼問題 if debug_enabled:
if sys.platform == 'win32': debug_log("🚀 啟動互動式回饋收集 MCP 服務器")
debug_log("偵測到 Windows 環境,設置 stdio 二進制模式") debug_log(f" 服務器名稱: {SERVER_NAME}")
try: debug_log(f" 版本: {__version__}")
# 設置 stdin/stdout 為二進制模式,避免 Windows 下的編碼問題 debug_log(f" 平台: {sys.platform}")
import msvcrt debug_log(f" 編碼初始化: {'成功' if _encoding_initialized else '失敗'}")
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) debug_log(f" 遠端環境: {is_remote_environment()}")
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) debug_log(f" GUI 可用: {can_use_gui()}")
debug_log("Windows stdio 二進制模式設置成功") debug_log(f" 建議介面: {'Web UI' if is_remote_environment() or not can_use_gui() else 'Qt GUI'}")
debug_log(" 等待來自 AI 助手的調用...")
# 重新包裝 stdin/stdout 為 UTF-8 編碼的文本流 debug_log("準備啟動 MCP 伺服器...")
sys.stdin = io.TextIOWrapper(sys.stdin.detach(), encoding='utf-8', errors='replace') debug_log("調用 mcp.run()...")
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='utf-8', errors='replace', newline='')
debug_log("Windows stdio UTF-8 包裝設置成功")
except Exception as e:
debug_log(f"Windows stdio 設置失敗,使用預設模式: {e}")
else:
# 非 Windows 系統:確保使用 UTF-8 編碼
try:
if hasattr(sys.stdin, 'reconfigure'):
sys.stdin.reconfigure(encoding='utf-8', errors='replace')
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
debug_log("非 Windows 系統 UTF-8 編碼設置成功")
except Exception as e:
debug_log(f"UTF-8 編碼設置失敗: {e}")
# 確保 stderr 使用 UTF-8 編碼(用於 debug 訊息)
if hasattr(sys.stderr, 'reconfigure'):
try:
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
debug_log("stderr UTF-8 編碼設置成功")
except Exception as e:
debug_log(f"stderr 編碼設置失敗: {e}")
# 強制 stdout 立即刷新,確保 JSON-RPC 消息及時發送
sys.stdout.reconfigure(line_buffering=True) if hasattr(sys.stdout, 'reconfigure') else None
try: try:
# 使用正確的 FastMCP API
mcp.run() mcp.run()
except KeyboardInterrupt: except KeyboardInterrupt:
debug_log("收到中斷信號,正常退出") if debug_enabled:
debug_log("收到中斷信號,正常退出")
sys.exit(0) sys.exit(0)
except Exception as e: except Exception as e:
debug_log(f"MCP 服務器啟動失敗: {e}") if debug_enabled:
import traceback debug_log(f"MCP 服務器啟動失敗: {e}")
debug_log(f"詳細錯誤: {traceback.format_exc()}") import traceback
debug_log(f"詳細錯誤: {traceback.format_exc()}")
sys.exit(1) sys.exit(1)

View File

@ -120,7 +120,7 @@ class WebFeedbackSession:
# 檢查文件大小 # 檢查文件大小
if img["size"] > MAX_IMAGE_SIZE: if img["size"] > MAX_IMAGE_SIZE:
print(f"[DEBUG] 圖片 {img['name']} 超過大小限制,跳過") debug_log(f"圖片 {img['name']} 超過大小限制,跳過")
continue continue
# 解碼 base64 數據 # 解碼 base64 數據
@ -128,13 +128,13 @@ class WebFeedbackSession:
try: try:
image_bytes = base64.b64decode(img["data"]) image_bytes = base64.b64decode(img["data"])
except Exception as e: except Exception as e:
print(f"[DEBUG] 圖片 {img['name']} base64 解碼失敗: {e}") debug_log(f"圖片 {img['name']} base64 解碼失敗: {e}")
continue continue
else: else:
image_bytes = img["data"] image_bytes = img["data"]
if len(image_bytes) == 0: if len(image_bytes) == 0:
print(f"[DEBUG] 圖片 {img['name']} 數據為空,跳過") debug_log(f"圖片 {img['name']} 數據為空,跳過")
continue continue
processed_images.append({ processed_images.append({
@ -143,10 +143,10 @@ class WebFeedbackSession:
"size": len(image_bytes) "size": len(image_bytes)
}) })
print(f"[DEBUG] 圖片 {img['name']} 處理成功,大小: {len(image_bytes)} bytes") debug_log(f"圖片 {img['name']} 處理成功,大小: {len(image_bytes)} bytes")
except Exception as e: except Exception as e:
print(f"[DEBUG] 圖片處理錯誤: {e}") debug_log(f"圖片處理錯誤: {e}")
continue continue
return processed_images return processed_images
@ -207,7 +207,7 @@ class WebFeedbackSession:
) )
except Exception as e: except Exception as e:
print(f"命令執行錯誤: {e}") debug_log(f"命令執行錯誤: {e}")
finally: finally:
self.process = None self.process = None
@ -293,9 +293,9 @@ class WebUIManager:
await self.handle_websocket_message(session, data) await self.handle_websocket_message(session, data)
except WebSocketDisconnect: except WebSocketDisconnect:
print(f"WebSocket 斷開連接: {session_id}") debug_log(f"WebSocket 斷開連接: {session_id}")
except Exception as e: except Exception as e:
print(f"WebSocket 錯誤: {e}") debug_log(f"WebSocket 錯誤: {e}")
finally: finally:
session.websocket = None session.websocket = None
@ -364,7 +364,7 @@ class WebUIManager:
try: try:
webbrowser.open(url) webbrowser.open(url)
except Exception as e: except Exception as e:
print(f"無法開啟瀏覽器: {e}") debug_log(f"無法開啟瀏覽器: {e}")
def _get_simple_index_html(self) -> str: def _get_simple_index_html(self) -> str:
"""簡單的首頁 HTML""" """簡單的首頁 HTML"""
@ -446,7 +446,7 @@ async def launch_web_feedback_ui(project_directory: str, summary: str) -> dict:
session_id = manager.create_session(project_directory, summary) session_id = manager.create_session(project_directory, summary)
session_url = f"http://{manager.host}:{manager.port}/session/{session_id}" session_url = f"http://{manager.host}:{manager.port}/session/{session_id}"
print(f"🌐 Web UI 已啟動: {session_url}") debug_log(f"🌐 Web UI 已啟動: {session_url}")
# 開啟瀏覽器 # 開啟瀏覽器
manager.open_browser(session_url) manager.open_browser(session_url)
@ -461,14 +461,14 @@ async def launch_web_feedback_ui(project_directory: str, summary: str) -> dict:
return result return result
except TimeoutError: except TimeoutError:
print("⏰ 等待用戶回饋超時") debug_log("⏰ 等待用戶回饋超時")
return { return {
"logs": "", "logs": "",
"interactive_feedback": "回饋超時", "interactive_feedback": "回饋超時",
"images": [] "images": []
} }
except Exception as e: except Exception as e:
print(f"❌ Web UI 錯誤: {e}") debug_log(f"❌ Web UI 錯誤: {e}")
return { return {
"logs": "", "logs": "",
"interactive_feedback": f"錯誤: {str(e)}", "interactive_feedback": f"錯誤: {str(e)}",
@ -507,7 +507,7 @@ if __name__ == "__main__":
session_id = manager.create_session(args.project_directory, args.summary) session_id = manager.create_session(args.project_directory, args.summary)
session_url = f"http://{args.host}:{args.port}/session/{session_id}" session_url = f"http://{args.host}:{args.port}/session/{session_id}"
print(f"🌐 Web UI 已啟動: {session_url}") debug_log(f"🌐 Web UI 已啟動: {session_url}")
manager.open_browser(session_url) manager.open_browser(session_url)
try: try:
@ -515,6 +515,19 @@ if __name__ == "__main__":
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n👋 Web UI 已停止") debug_log("\n👋 Web UI 已停止")
asyncio.run(main()) asyncio.run(main())
# === 工具函數 ===
def debug_log(message: str) -> None:
"""輸出調試訊息到標準錯誤,避免污染標準輸出"""
# 只在啟用調試模式時才輸出,避免干擾 MCP 通信
if not os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on"):
return
try:
print(f"[WEB_UI] {message}", file=sys.stderr, flush=True)
except Exception:
# 靜默失敗,不影響主程序
pass

742
uv.lock generated

File diff suppressed because it is too large Load Diff