將 gui 和 webui 的對話框加入 ctrl +v 就能複製貼上圖片的更能

This commit is contained in:
Minidoracat 2025-06-02 13:13:08 +08:00
parent f148841cc0
commit 0f56c7c5fb
12 changed files with 331 additions and 350 deletions

View File

@ -13,6 +13,7 @@ MCP Interactive Feedback Enhanced - 主程式入口
import sys
import argparse
import os
def main():
"""主程式入口點"""
@ -56,6 +57,9 @@ def run_server():
def run_tests(args):
"""執行測試"""
# 啟用調試模式以顯示測試過程
os.environ["MCP_DEBUG"] = "true"
if args.web:
print("🧪 執行 Web UI 測試...")
from .test_web_ui import test_web_ui, interactive_demo

View File

@ -745,6 +745,24 @@ class FeedbackWindow(QMainWindow):
if hasattr(self, 'summary_title'):
self.summary_title.setText(t('ai_summary'))
# 更新摘要內容(如果是測試摘要)
if hasattr(self, 'summary_text'):
# 檢查是否為測試摘要,需要動態翻譯
if self._is_test_summary():
# 判斷是哪種測試類型並重新獲取翻譯
if '圖片預覽' in self.summary or 'Image Preview' in self.summary or '图片预览' in self.summary:
# Qt GUI 測試
translated_summary = t('test.qtGuiSummary')
elif 'Web UI' in self.summary:
# Web UI 測試
translated_summary = t('test.webUiSummary')
else:
translated_summary = self.summary
self.summary_text.setPlainText(translated_summary)
# 更新儲存的摘要以保持一致
self.summary = translated_summary
# 更新專案目錄標籤
if hasattr(self, 'project_label'):
self.project_label.setText(f"{t('project_directory')}: {self.project_dir}")
@ -780,6 +798,38 @@ class FeedbackWindow(QMainWindow):
if hasattr(self, 'output_title'):
self.output_title.setText(t('command_output'))
def _is_test_summary(self) -> bool:
"""檢查是否為測試摘要,使用更嚴格的檢測邏輯"""
# 更嚴格的測試摘要特徵組合檢測
test_patterns = [
# Qt GUI 測試特徵
('測試 Qt GUI 功能', '🎯 **功能測試項目'),
('Test Qt GUI Functionality', '🎯 **Test Items'),
('测试 Qt GUI 功能', '🎯 **功能测试项目'),
# Web UI 測試特徵
('測試 Web UI 功能', '🎯 **功能測試項目'),
('Test Web UI Functionality', '🎯 **Test Items'),
('测试 Web UI 功能', '🎯 **功能测试项目'),
# 具體的測試項目描述
('圖片上傳和預覽', '智能 Ctrl+V 圖片貼上'),
('Image upload and preview', 'Smart Ctrl+V image paste'),
('图片上传和预览', '智能 Ctrl+V 图片粘贴'),
# WebSocket 和服務器啟動描述
('WebSocket 即時通訊', 'Web UI 服務器啟動'),
('WebSocket real-time communication', 'Web UI server startup'),
('WebSocket 即时通讯', 'Web UI 服务器启动')
]
# 必須同時包含模式中的兩個特徵才認為是測試摘要
for pattern1, pattern2 in test_patterns:
if pattern1 in self.summary and pattern2 in self.summary:
return True
return False
def _update_image_upload_texts(self) -> None:
"""更新圖片上傳元件的文字"""
if hasattr(self, 'image_upload'):
@ -812,12 +862,12 @@ class FeedbackWindow(QMainWindow):
summary_layout.addLayout(header_layout)
# 摘要內容(可滾動的文本區域)
summary_text = QTextEdit()
summary_text.setPlainText(self.summary)
summary_text.setReadOnly(True)
summary_text.setMaximumHeight(120)
summary_layout.addWidget(summary_text)
# 摘要內容(可滾動的文本區域)- 儲存為實例變數以支援動態更新
self.summary_text = QTextEdit()
self.summary_text.setPlainText(self.summary)
self.summary_text.setReadOnly(True)
self.summary_text.setMaximumHeight(120)
summary_layout.addWidget(self.summary_text)
layout.addWidget(summary_group)

View File

@ -268,6 +268,10 @@ class I18nManager:
'language_zh_tw': 'languageNames.zhTw',
'language_en': 'languageNames.en',
'language_zh_cn': 'languageNames.zhCn',
# 測試
'test_qt_gui_summary': 'test.qtGuiSummary',
'test_web_ui_summary': 'test.webUiSummary',
}
# 檢查是否有對應的新鍵

View File

@ -82,5 +82,9 @@
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
},
"test": {
"qtGuiSummary": "🎯 Image Preview and Window Adjustment Test\n\nThis is a test session to verify the following features:\n\n✅ Test Items:\n1. Image upload and preview functionality\n2. Image X delete button in top-right corner\n3. Free window resizing\n4. Flexible splitter adjustment\n5. Dynamic layout of all areas\n6. Smart Ctrl+V image paste functionality\n\n📋 Test Steps:\n1. Try uploading some images (drag & drop, file selection, clipboard)\n2. Check if image preview displays correctly\n3. Click the X button in the top-right corner of images to delete them\n4. Try resizing the window, check if it can be freely adjusted\n5. Drag the splitter to adjust area sizes\n6. Press Ctrl+V in the text box to test smart paste functionality\n7. Provide any feedback or issues found\n\nPlease test these features and provide feedback!",
"webUiSummary": "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 paste\n- Multi-language interface switching\n\n📋 **Test Steps:**\n1. Test image upload (drag & drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart paste\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!"
}
}

View File

@ -82,5 +82,9 @@
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
},
"test": {
"qtGuiSummary": "🎯 图片预览和窗口调整测试\n\n这是一个测试会话用于验证以下功能\n\n✅ 功能测试项目:\n1. 图片上传和预览功能\n2. 图片右上角X删除按钮\n3. 窗口自由调整大小\n4. 分割器的灵活调整\n5. 各区域的动态布局\n6. 智能 Ctrl+V 图片粘贴功能\n\n📋 测试步骤:\n1. 尝试上传一些图片(拖拽、文件选择、剪贴板)\n2. 检查图片预览是否正常显示\n3. 点击图片右上角的X按钮删除图片\n4. 尝试调整窗口大小,检查是否可以自由调整\n5. 拖动分割器调整各区域大小\n6. 在文本框内按 Ctrl+V 测试智能粘贴功能\n7. 提供任何回馈或发现的问题\n\n请测试这些功能并提供回馈",
"webUiSummary": "测试 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请测试这些功能并提供回馈"
}
}

View File

@ -77,10 +77,14 @@
"fileTooLarge": "文件過大(最大 1MB"
},
"aiSummary": "AI 工作摘要",
"languageSelector": "🌐 語言",
"languageSelector": "🌐 語言選擇",
"languageNames": {
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
},
"test": {
"qtGuiSummary": "🎯 圖片預覽和視窗調整測試\n\n這是一個測試會話用於驗證以下功能\n\n✅ 功能測試項目:\n1. 圖片上傳和預覽功能\n2. 圖片右上角X刪除按鈕\n3. 視窗自由調整大小\n4. 分割器的靈活調整\n5. 各區域的動態佈局\n6. 智能 Ctrl+V 圖片貼上功能\n\n📋 測試步驟:\n1. 嘗試上傳一些圖片(拖拽、文件選擇、剪貼板)\n2. 檢查圖片預覽是否正常顯示\n3. 點擊圖片右上角的X按鈕刪除圖片\n4. 嘗試調整視窗大小,檢查是否可以自由調整\n5. 拖動分割器調整各區域大小\n6. 在文字框內按 Ctrl+V 測試智能貼上功能\n7. 提供任何回饋或發現的問題\n\n請測試這些功能並提供回饋",
"webUiSummary": "測試 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請測試這些功能並提供回饋"
}
}

View File

@ -373,8 +373,7 @@ def launch_gui(project_dir: str, summary: str) -> dict:
async def interactive_feedback(
project_directory: Annotated[str, Field(description="專案目錄路徑")] = ".",
summary: Annotated[str, Field(description="AI 工作完成的摘要說明")] = "我已完成了您請求的任務。",
timeout: Annotated[int, Field(description="等待用戶回饋的超時時間(秒)")] = 600,
force_web_ui: Annotated[bool, Field(description="強制使用 Web UI用於測試或特殊需求")] = False
timeout: Annotated[int, Field(description="等待用戶回饋的超時時間(秒)")] = 600
) -> List:
"""
收集用戶的互動回饋支援文字和圖片
@ -382,7 +381,7 @@ async def interactive_feedback(
此工具會自動偵測運行環境
- 遠端環境使用 Web UI
- 本地環境使用 Qt GUI
- 可透過 force_web_ui 參數或 FORCE_WEB 環境變數強制使用 Web UI
- 可透過 FORCE_WEB 環境變數強制使用 Web UI
用戶可以
1. 執行命令來驗證結果
@ -390,6 +389,10 @@ async def interactive_feedback(
3. 上傳圖片作為回饋
4. 查看 AI 的工作摘要
介面控制按優先級排序
1. **FORCE_WEB 環境變數** mcp.json 中設置 "FORCE_WEB": "true"
2. 自動檢測根據運行環境自動選擇
調試模式
- 設置環境變數 MCP_DEBUG=true 可啟用詳細調試輸出
- 生產環境建議關閉調試模式以避免輸出干擾
@ -398,26 +401,26 @@ async def interactive_feedback(
project_directory: 專案目錄路徑
summary: AI 工作完成的摘要說明
timeout: 等待用戶回饋的超時時間預設為 600 10 分鐘
force_web_ui: 強制使用 Web UI即使在本地環境也使用 Web UI用於測試
Returns:
List: 包含 TextContent MCPImage 對象的列表
"""
# 檢查環境變數,如果設定了 FORCE_WEB 就覆蓋 force_web_ui 參數
# 檢查環境變數 FORCE_WEB
force_web = False
env_force_web = os.getenv("FORCE_WEB", "").lower()
if env_force_web in ("true", "1", "yes", "on"):
force_web_ui = True
force_web = True
debug_log("環境變數 FORCE_WEB 已啟用,強制使用 Web UI")
elif env_force_web in ("false", "0", "no", "off"):
force_web_ui = False
force_web = False
debug_log("環境變數 FORCE_WEB 已停用,使用預設邏輯")
# 環境偵測
is_remote = is_remote_environment()
can_gui = can_use_gui()
use_web_ui = is_remote or not can_gui or force_web_ui
use_web_ui = is_remote or not can_gui or force_web
debug_log(f"環境偵測結果 - 遠端: {is_remote}, GUI 可用: {can_gui}, 強制 Web UI: {force_web_ui}")
debug_log(f"環境偵測結果 - 遠端: {is_remote}, GUI 可用: {can_gui}, 強制 Web UI: {force_web}")
debug_log(f"決定使用介面: {'Web UI' if use_web_ui else 'Qt GUI'}")
try:

View File

@ -26,185 +26,27 @@ class I18nManager {
}
/**
* 獲取內嵌的備用翻譯
* 獲取內嵌的備用翻譯僅保留基本錯誤訊息
*/
_getEmbeddedTranslations() {
return {
'zh-TW': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: '專案目錄',
language: '語言'
},
tabs: {
feedback: '💬 回饋',
command: '⚡ 命令'
},
feedback: {
title: '💬 您的回饋',
description: '請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。',
placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交回饋\n• 按 Ctrl+V 可直接貼上剪貼簿圖片'
},
command: {
title: '⚡ 命令執行',
description: '您可以在此執行系統命令來驗證結果或獲取更多資訊。',
placeholder: '輸入要執行的命令...',
output: '命令輸出'
},
images: {
title: '🖼️ 圖片附件(可選)',
status: '已選擇 {count} 張圖片',
statusWithSize: '已選擇 {count} 張圖片 (總計 {size})',
dragHint: '🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '確定要移除圖片 "{filename}" 嗎?',
deleteTitle: '確認刪除'
},
buttons: {
selectFiles: '📁 選擇文件',
pasteClipboard: '📋 剪貼板',
clearAll: '✕ 清除',
runCommand: '▶️ 執行',
submitFeedback: '✅ 提交回饋',
cancel: '❌ 取消'
},
status: {
uploading: '上傳中...',
uploadSuccess: '上傳成功',
uploadFailed: '上傳失敗',
commandRunning: '命令執行中...',
commandFinished: '命令執行完成',
pasteSuccess: '已從剪貼板貼上圖片',
pasteFailed: '無法從剪貼板獲取圖片',
paste_no_image: '剪貼簿中沒有圖片可貼上',
paste_image_from_textarea: '已將圖片從文字框智能貼到圖片區域',
invalidFileType: '不支援的文件類型',
fileTooLarge: '文件過大(最大 1MB'
},
aiSummary: '📋 AI 工作摘要',
languageSelector: '🌐 語言選擇',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
app: { title: 'Interactive Feedback MCP' },
loading: '載入中...',
error: '載入失敗',
retry: '重試'
},
'en': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: 'Project Directory',
language: 'Language'
},
tabs: {
feedback: '💬 Feedback',
command: '⚡ Commands'
},
feedback: {
title: '💬 Your Feedback',
description: 'Please enter your feedback, suggestions, or questions here. Your input helps AI better understand your needs.',
placeholder: 'Please enter your feedback, suggestions, or questions here...\n\n💡 Tips:\n• Press Ctrl+Enter to submit quickly\n• Press Ctrl+V to paste images from clipboard'
},
command: {
title: '⚡ Command Execution',
description: 'You can execute system commands here to verify results or get additional information.',
placeholder: 'Enter command to execute...',
output: 'Command Output'
},
images: {
title: '🖼️ Image Attachments (Optional)',
status: '{count} images selected',
statusWithSize: '{count} images selected (Total {size})',
dragHint: '🎯 Drag images here or press Ctrl+V to paste from clipboard (PNG, JPG, JPEG, GIF, BMP, WebP)',
deleteConfirm: 'Are you sure you want to remove image "{filename}"?',
deleteTitle: 'Confirm Delete'
},
buttons: {
selectFiles: '📁 Select Files',
pasteClipboard: '📋 Clipboard',
clearAll: '✕ Clear',
runCommand: '▶️ Run',
submitFeedback: '✅ Submit Feedback',
cancel: '❌ Cancel'
},
status: {
uploading: 'Uploading...',
uploadSuccess: 'Upload successful',
uploadFailed: 'Upload failed',
commandRunning: 'Command running...',
commandFinished: 'Command finished',
pasteSuccess: 'Image pasted from clipboard',
pasteFailed: 'Failed to get image from clipboard',
paste_no_image: 'No image to paste from clipboard',
paste_image_from_textarea: 'Image pasted from text area to image area',
invalidFileType: 'Unsupported file type',
fileTooLarge: 'File too large (max 1MB)'
},
aiSummary: '📋 AI Work Summary',
languageSelector: '🌐 Language',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
app: { title: 'Interactive Feedback MCP' },
loading: 'Loading...',
error: 'Loading failed',
retry: 'Retry'
},
'zh-CN': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: '项目目录',
language: '语言'
},
tabs: {
feedback: '💬 反馈',
command: '⚡ 命令'
},
feedback: {
title: '💬 您的反馈',
description: '请在这里输入您的反馈、建议或问题。您的意见将帮助 AI 更好地理解您的需求。',
placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交反馈\n• 按 Ctrl+V 可直接贴上剪贴板图片'
},
command: {
title: '⚡ 命令执行',
description: '您可以在此执行系统命令来验证结果或获取更多信息。',
placeholder: '输入要执行的命令...',
output: '命令输出'
},
images: {
title: '🖼️ 图片附件(可选)',
status: '已选择 {count} 张图片',
statusWithSize: '已选择 {count} 张图片 (总计 {size})',
dragHint: '🎯 拖拽图片到这里 或 按 Ctrl+V 贴上剪贴板图片 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '确定要移除图片 "{filename}" 吗?',
deleteTitle: '确认删除'
},
buttons: {
selectFiles: '📁 选择文件',
pasteClipboard: '📋 剪贴板',
clearAll: '✕ 清除',
runCommand: '▶️ 执行',
submitFeedback: '✅ 提交反馈',
cancel: '❌ 取消'
},
status: {
uploading: '上传中...',
uploadSuccess: '上传成功',
uploadFailed: '上传失败',
commandRunning: '命令执行中...',
commandFinished: '命令执行完成',
pasteSuccess: '已从剪贴板粘贴图片',
pasteFailed: '无法从剪贴板获取图片',
paste_no_image: '剪贴板中没有图片可粘贴',
paste_image_from_textarea: '已将图片从文字框智能贴到图片区域',
invalidFileType: '不支持的文件类型',
fileTooLarge: '文件过大(最大 1MB'
},
aiSummary: '📋 AI 工作摘要',
languageSelector: '🌐 语言选择',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
app: { title: 'Interactive Feedback MCP' },
loading: '加载中...',
error: '加载失败',
retry: '重试'
}
};
}

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="zh-TW" id="html-root">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -103,7 +104,8 @@
}
}
.feedback-section, .summary-section {
.feedback-section,
.summary-section {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
@ -180,7 +182,8 @@
color: var(--text-primary);
}
.text-input, .command-input {
.text-input,
.command-input {
width: 100%;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
@ -199,13 +202,15 @@
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
}
.text-input:focus, .command-input:focus {
.text-input:focus,
.command-input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
}
.text-input::placeholder, .command-input::placeholder {
.text-input::placeholder,
.command-input::placeholder {
color: var(--text-secondary);
}
@ -333,7 +338,7 @@
border-radius: 6px;
padding: 15px;
background: var(--bg-tertiary);
background-image:
background-image:
linear-gradient(90deg, rgba(70, 70, 71, 0.1) 1px, transparent 1px),
linear-gradient(rgba(70, 70, 71, 0.1) 1px, transparent 1px);
background-size: 120px 120px;
@ -509,11 +514,11 @@
.container {
padding: 15px;
}
.header-content {
padding: 0 15px;
}
.image-preview-area {
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 10px;
@ -553,14 +558,14 @@
flex-direction: column;
gap: 15px;
}
.image-preview-area {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 8px;
padding: 10px;
max-height: 300px;
}
.image-preview {
height: 80px;
}
@ -572,7 +577,7 @@
gap: 15px;
padding: 20px;
}
.image-preview {
height: 120px;
}
@ -602,6 +607,7 @@
}
</style>
</head>
<body>
<div class="header">
<div class="header-content">
@ -653,11 +659,8 @@
<div class="section-description" id="feedbackDescription">
請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。
</div>
<textarea
id="feedbackText"
class="text-input"
placeholder="請在這裡輸入您的回饋、建議或問題...&#10;&#10;💡 小提示:&#10;• 按 Ctrl+Enter 可快速提交回饋&#10;• 按 Ctrl+V 可直接貼上剪貼簿圖片"
></textarea>
<textarea id="feedbackText" class="text-input"
placeholder="請在這裡輸入您的回饋、建議或問題...&#10;&#10;💡 小提示:&#10;• 按 Ctrl+Enter 可快速提交回饋&#10;• 按 Ctrl+V 可直接貼上剪貼簿圖片"></textarea>
</div>
<!-- 圖片上傳區域 -->
@ -665,7 +668,8 @@
<h3 id="imagesTitle">🖼️ 圖片附件(可選)</h3>
<div class="upload-buttons">
<button class="upload-btn" onclick="selectFiles()" id="selectFilesBtn">📁 選擇文件</button>
<button class="upload-btn success" onclick="pasteFromClipboard()" id="pasteBtn">📋 剪貼板</button>
<button class="upload-btn success" onclick="pasteFromClipboard()" id="pasteBtn">📋
剪貼板</button>
<button class="upload-btn danger" onclick="clearAllImages()" id="clearBtn">❌ 清除</button>
</div>
<div class="drop-zone" id="dropZone">
@ -685,13 +689,8 @@
</div>
<div class="command-section">
<div class="command-input-wrapper">
<input
type="text"
id="commandInput"
class="command-input"
placeholder="輸入要執行的命令..."
onkeypress="if(event.key==='Enter') runCommand()"
>
<input type="text" id="commandInput" class="command-input" placeholder="輸入要執行的命令..."
onkeypress="if(event.key==='Enter') runCommand()">
</div>
<button class="run-button" onclick="runCommand()" id="runBtn">▶️ 執行</button>
</div>
@ -716,7 +715,7 @@
<!-- 引入國際化模組 -->
<script src="/static/i18n.js"></script>
<script>
// 全域變數
let selectedImages = [];
@ -742,9 +741,9 @@
function initWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/{{ session_id }}`;
console.log('嘗試連接 WebSocket:', wsUrl);
try {
ws = new WebSocket(wsUrl);
setupWebSocketHandlers();
@ -755,14 +754,14 @@
}
function setupWebSocketHandlers() {
ws.onopen = function() {
ws.onopen = function () {
console.log('WebSocket 連接成功');
isConnected = true;
reconnectAttempts = 0;
showNotification('WebSocket 連接成功', 'success');
};
ws.onmessage = function(event) {
ws.onmessage = function (event) {
try {
const data = JSON.parse(event.data);
handleWebSocketMessage(data);
@ -772,17 +771,17 @@
}
};
ws.onclose = function(event) {
ws.onclose = function (event) {
console.log('WebSocket 連接關閉:', event.code, event.reason);
isConnected = false;
// 自動重連(除非是正常關閉)
if (event.code !== 1000 && reconnectAttempts < maxReconnectAttempts) {
setTimeout(attemptReconnect, reconnectDelay);
}
};
ws.onerror = function(error) {
ws.onerror = function (error) {
console.error('WebSocket 錯誤:', error);
handleConnectionError();
};
@ -797,23 +796,31 @@
reconnectAttempts++;
console.log(`嘗試重連 (${reconnectAttempts}/${maxReconnectAttempts})`);
initWebSocket();
}
function handleConnectionError() {
isConnected = false;
if (reconnectAttempts < maxReconnectAttempts) {
setTimeout(attemptReconnect, reconnectDelay);
}
}
// 頁面初始化
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', async function () {
// 先初始化 i18n 載入翻譯
try {
await window.initI18n();
console.log('[I18N] 翻譯載入完成');
} catch (error) {
console.warn('[I18N] 翻譯載入失敗,使用內嵌翻譯:', error);
}
// 初始化 WebSocket 連接
initWebSocket();
// 初始化界面
initializeApp();
setupEventListeners();
@ -839,7 +846,7 @@
if (languageSelect) {
languageSelect.value = currentLanguage;
}
// 更新 HTML lang 屬性
updateHtmlLang();
}
@ -848,7 +855,7 @@
// 語言選擇器事件
const languageSelect = document.getElementById('languageSelect');
if (languageSelect) {
languageSelect.addEventListener('change', function(e) {
languageSelect.addEventListener('change', function (e) {
changeLanguage(e.target.value);
});
}
@ -863,7 +870,7 @@
if (elements.runCommandBtn) {
elements.runCommandBtn.addEventListener('click', runCommand);
}
// 命令輸入框 Enter 鍵
if (elements.commandInput) {
elements.commandInput.addEventListener('keydown', (e) => {
@ -872,7 +879,7 @@
}
});
}
// 回饋提交
if (elements.submitBtn) {
elements.submitBtn.addEventListener('click', submitFeedback);
@ -880,7 +887,7 @@
if (elements.cancelBtn) {
elements.cancelBtn.addEventListener('click', cancelFeedback);
}
// 快捷鍵支援
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
@ -888,7 +895,7 @@
submitFeedback();
}
});
// 窗口關閉時清理 WebSocket
window.addEventListener('beforeunload', () => {
if (ws && isConnected) {
@ -906,7 +913,7 @@
'zh-CN': 'zh-CN',
'en': 'en'
};
htmlRoot.setAttribute('lang', langMap[currentLanguage] || 'en');
}
}
@ -914,17 +921,17 @@
function applyTranslations() {
// 更新頁面標題
document.title = t('app_title');
// 更新標題區域
const pageTitle = document.getElementById('pageTitle');
if (pageTitle) pageTitle.textContent = t('app_title');
const projectDirLabel = document.getElementById('projectDirLabel');
if (projectDirLabel) projectDirLabel.textContent = t('project_directory');
const languageLabel = document.getElementById('languageLabel');
if (languageLabel) languageLabel.textContent = t('language_selector') + ':';
// 更新語言選項
const languageSelect = document.getElementById('languageSelect');
if (languageSelect) {
@ -934,58 +941,95 @@
option.textContent = window.i18n.getLanguageDisplayName(value);
});
}
// 更新摘要區域
const summaryTitle = document.getElementById('summaryTitle');
if (summaryTitle) summaryTitle.innerHTML = t('ai_summary');
// 動態更新 AI 工作摘要內容(如果是測試內容)
const summaryContent = document.getElementById('summaryContent');
if (summaryContent) {
const currentSummary = summaryContent.textContent || summaryContent.innerHTML;
// 更嚴格的測試摘要檢測邏輯 - 必須同時包含多個特徵
const isTestSummary = (
// Qt GUI 測試特徵組合
(currentSummary.includes('測試 Qt GUI 功能') && currentSummary.includes('🎯 **功能測試項目')) ||
(currentSummary.includes('Test Qt GUI Functionality') && currentSummary.includes('🎯 **Test Items')) ||
(currentSummary.includes('测试 Qt GUI 功能') && currentSummary.includes('🎯 **功能测试项目')) ||
// Web UI 測試特徵組合
(currentSummary.includes('測試 Web UI 功能') && currentSummary.includes('🎯 **功能測試項目')) ||
(currentSummary.includes('Test Web UI Functionality') && currentSummary.includes('🎯 **Test Items')) ||
(currentSummary.includes('测试 Web UI 功能') && currentSummary.includes('🎯 **功能测试项目')) ||
// 具體測試項目特徵組合
(currentSummary.includes('圖片上傳和預覽') && currentSummary.includes('智能 Ctrl+V 圖片貼上')) ||
(currentSummary.includes('Image upload and preview') && currentSummary.includes('Smart Ctrl+V image paste')) ||
(currentSummary.includes('图片上传和预览') && currentSummary.includes('智能 Ctrl+V 图片粘贴')) ||
// WebSocket 和服務器特徵組合
(currentSummary.includes('WebSocket 即時通訊') && currentSummary.includes('Web UI 服務器啟動')) ||
(currentSummary.includes('WebSocket real-time communication') && currentSummary.includes('Web UI server startup')) ||
(currentSummary.includes('WebSocket 即时通讯') && currentSummary.includes('Web UI 服务器启动'))
);
if (isTestSummary) {
// 使用對應語言的測試摘要
const testSummary = t('test.webUiSummary');
if (testSummary && testSummary !== 'test.webUiSummary') {
summaryContent.textContent = testSummary;
}
}
}
// 更新分頁標籤
const feedbackTabBtn = document.getElementById('feedbackTabBtn');
if (feedbackTabBtn) feedbackTabBtn.innerHTML = t('feedback_tab');
const commandTabBtn = document.getElementById('commandTabBtn');
if (commandTabBtn) commandTabBtn.innerHTML = t('command_tab');
// 更新回饋區域
const feedbackLabel = document.getElementById('feedbackLabel');
if (feedbackLabel) feedbackLabel.textContent = t('feedback_title');
const feedbackDescription = document.getElementById('feedbackDescription');
if (feedbackDescription) feedbackDescription.textContent = t('feedback_description');
// 更新命令區域
const commandLabel = document.getElementById('commandLabel');
if (commandLabel) commandLabel.textContent = t('command_title');
const commandDescription = document.getElementById('commandDescription');
if (commandDescription) commandDescription.textContent = t('command_description');
const runBtn = document.getElementById('runBtn');
if (runBtn) runBtn.innerHTML = t('btn_run_command');
// 更新圖片區域
const imagesTitle = document.getElementById('imagesTitle');
if (imagesTitle) imagesTitle.textContent = t('images_title');
const selectFilesBtn = document.getElementById('selectFilesBtn');
if (selectFilesBtn) selectFilesBtn.innerHTML = t('btn_select_files');
const pasteBtn = document.getElementById('pasteBtn');
if (pasteBtn) pasteBtn.innerHTML = t('btn_paste_clipboard');
const clearBtn = document.getElementById('clearBtn');
if (clearBtn) clearBtn.innerHTML = t('btn_clear_all');
const dropZone = document.getElementById('dropZone');
if (dropZone) dropZone.textContent = t('images_drag_hint');
// 更新按鈕
const cancelBtn = document.getElementById('cancelBtn');
if (cancelBtn) cancelBtn.innerHTML = t('btn_cancel');
const submitBtn = document.getElementById('submitBtn');
if (submitBtn) submitBtn.innerHTML = t('btn_submit_feedback');
// 更新圖片狀態和預覽區域
updateImageStatus();
updateImagePreviewArea();
@ -995,19 +1039,19 @@
// 更新輸入框的 placeholder
const feedbackText = document.getElementById('feedbackText');
if (feedbackText) feedbackText.placeholder = t('feedback_placeholder');
const commandInput = document.getElementById('commandInput');
if (commandInput) commandInput.placeholder = t('command_placeholder');
}
function setupKeyboardShortcuts() {
// Ctrl+Enter 快速提交
document.addEventListener('keydown', function(e) {
document.addEventListener('keydown', function (e) {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
submitFeedback();
}
// Ctrl+V 智能貼上
if (e.ctrlKey && e.key === 'v') {
// 先檢查剪貼簿是否包含圖片
@ -1016,7 +1060,7 @@
// 如果有圖片,無論焦點在哪裡都優先貼到圖片區域
e.preventDefault();
pasteFromClipboard();
// 提供額外的使用者提示
const activeElement = document.activeElement;
const isInTextArea = activeElement && activeElement.id === 'feedbackText';
@ -1027,11 +1071,11 @@
// 如果沒有圖片,檢查是否在文字輸入區域
const activeElement = document.activeElement;
const isTextInput = activeElement && (
activeElement.tagName === 'TEXTAREA' ||
activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.tagName === 'INPUT' ||
activeElement.contentEditable === 'true'
);
if (isTextInput) {
// 在文字輸入區域且剪貼簿只有文字,允許正常的文字貼上
// 不需要 preventDefault(),讓瀏覽器執行預設行為
@ -1073,15 +1117,15 @@
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// 移除所有按鈕的活動狀態
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
// 顯示選中的分頁
document.getElementById(tabName).classList.add('active');
// 設置對應按鈕為活動狀態
if (tabName === 'feedback') {
document.getElementById('feedbackTabBtn').classList.add('active');
@ -1093,7 +1137,7 @@
// 設置拖拽功能
function setupDragAndDrop() {
const dropZone = document.getElementById('dropZone');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
@ -1155,7 +1199,7 @@
}
const reader = new FileReader();
reader.onload = function(e) {
reader.onload = function (e) {
const imageData = {
id: Date.now() + Math.random(),
filename: file.name,
@ -1163,7 +1207,7 @@
size: file.size,
type: file.type
};
selectedImages.push(imageData);
updateImagePreview();
updateImageStatus();
@ -1177,7 +1221,7 @@
try {
const items = await navigator.clipboard.read();
let hasImage = false;
for (const item of items) {
for (const type of item.types) {
if (type.startsWith('image/')) {
@ -1188,7 +1232,7 @@
}
}
}
if (!hasImage) {
showStatusMessage(t('paste_failed'), 'error');
}
@ -1200,10 +1244,10 @@
// 圖片預覽更新
function updateImagePreview() {
const previewArea = document.getElementById('imagePreviewArea');
// 清除現有內容
previewArea.innerHTML = '';
if (selectedImages.length === 0) {
// 空狀態 - 移除 has-images 類別
previewArea.classList.remove('has-images');
@ -1211,15 +1255,15 @@
} else {
// 有圖片 - 添加 has-images 類別
previewArea.classList.add('has-images');
selectedImages.forEach(img => {
const preview = document.createElement('div');
preview.className = 'image-preview';
const imgElement = document.createElement('img');
imgElement.src = `data:${img.type};base64,${img.data}`;
imgElement.alt = img.filename;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'image-delete';
deleteBtn.innerHTML = '×';
@ -1229,12 +1273,12 @@
removeImage(img.id);
}
};
preview.appendChild(imgElement);
preview.appendChild(deleteBtn);
previewArea.appendChild(preview);
});
// 添加網格指示器
updateGridIndicator();
}
@ -1242,19 +1286,19 @@
function updateImagePreviewArea() {
const previewArea = document.getElementById('imagePreviewArea');
if (selectedImages.length === 0) {
// 計算當前可顯示的列數
const containerWidth = previewArea.clientWidth - 30; // 減去 padding
const minItemWidth = 100; // 最小項目寬度
const gap = 12; // 間距
const maxColumns = Math.floor((containerWidth + gap) / (minItemWidth + gap));
const actualColumns = Math.max(1, maxColumns);
const emptyText = `📋 圖片預覽區域\n\n💡 目前寬度可顯示 ${actualColumns} 列圖片\n區域寬度: ${Math.round(containerWidth)}px`;
previewArea.setAttribute('data-empty-text', emptyText);
// 移除網格指示器
const existingIndicator = previewArea.querySelector('.preview-grid-indicator');
if (existingIndicator) {
@ -1265,20 +1309,20 @@
function updateGridIndicator() {
const previewArea = document.getElementById('imagePreviewArea');
// 移除現有指示器
const existingIndicator = previewArea.querySelector('.preview-grid-indicator');
if (existingIndicator) {
existingIndicator.remove();
}
// 計算當前列數
const containerWidth = previewArea.clientWidth - 30;
const minItemWidth = 100;
const gap = 12;
const maxColumns = Math.floor((containerWidth + gap) / (minItemWidth + gap));
const actualColumns = Math.max(1, maxColumns);
// 添加新指示器
const indicator = document.createElement('div');
indicator.className = 'preview-grid-indicator';
@ -1294,8 +1338,8 @@
function clearAllImages() {
if (selectedImages.length > 0) {
const confirmMessage = t('images_delete_confirm', {
filename: `${selectedImages.length} ${t('images_status', { count: selectedImages.length }).match(/\d+\s+(\S+)/)[1]}`
const confirmMessage = t('images_delete_confirm', {
filename: `${selectedImages.length} ${t('images_status', { count: selectedImages.length }).match(/\d+\s+(\S+)/)[1]}`
});
if (confirm(confirmMessage)) {
selectedImages = [];
@ -1308,19 +1352,19 @@
function updateImageStatus() {
const count = selectedImages.length;
const statusElement = document.getElementById('imageStatus');
if (count === 0) {
statusElement.textContent = t('images_status', { count: 0 });
} else {
const totalSize = selectedImages.reduce((sum, img) => sum + img.size, 0);
let sizeStr;
if (totalSize >= 1024 * 1024) {
sizeStr = `${(totalSize / (1024 * 1024)).toFixed(1)} MB`;
} else {
sizeStr = `${(totalSize / 1024).toFixed(1)} KB`;
}
statusElement.textContent = t('images_status_with_size', { count, size: sizeStr });
}
}
@ -1328,7 +1372,7 @@
// 命令執行功能
function runCommand() {
if (!elements.commandInput) return;
const command = elements.commandInput.value.trim();
if (!command) return;
@ -1338,13 +1382,13 @@
}
console.log('執行命令:', command);
// 清空之前的輸出
if (elements.commandOutput) {
elements.commandOutput.textContent = '';
elements.commandOutput.style.display = 'block';
}
// 更新 UI 狀態
isCommandRunning = true;
if (elements.runCommandBtn) {
@ -1354,13 +1398,13 @@
if (elements.commandInput) {
elements.commandInput.disabled = true;
}
// 發送命令執行請求
const success = sendWebSocketMessage({
type: 'run_command',
command: command
});
if (!success) {
handleCommandFinished(-1);
}
@ -1375,7 +1419,7 @@
function handleCommandFinished(exitCode) {
console.log('命令執行完成,退出碼:', exitCode);
isCommandRunning = false;
if (elements.runCommandBtn) {
elements.runCommandBtn.style.display = 'inline-flex';
@ -1384,20 +1428,20 @@
if (elements.commandInput) {
elements.commandInput.disabled = false;
}
const statusText = exitCode === 0 ? '命令執行成功' : '命令執行失敗';
const notificationType = exitCode === 0 ? 'success' : 'error';
showNotification(statusText, notificationType);
appendCommandOutput(`\n--- 命令執行完成 (退出碼: ${exitCode}) ---\n`);
}
// 回饋提交功能
function submitFeedback() {
if (!elements.feedbackTextarea) return;
const feedback = elements.feedbackTextarea.value.trim();
if (!feedback && selectedImages.length === 0) {
showStatusMessage(t('feedback_placeholder').split('\n')[0], 'error');
return;
@ -1409,7 +1453,7 @@
}
console.log('提交回饋:', feedback);
// 顯示提交中狀態
if (elements.submitBtn) {
elements.submitBtn.textContent = '提交中...';
@ -1428,7 +1472,7 @@
if (success) {
showNotification('回饋已提交', 'success');
// 短暫延遲後關閉窗口
setTimeout(() => {
if (ws) {
@ -1459,14 +1503,14 @@
const statusElement = document.getElementById('statusMessage');
statusElement.textContent = message;
statusElement.className = `status-message ${type} show`;
setTimeout(() => {
statusElement.classList.remove('show');
}, 3000);
}
// 監聽窗口大小變化
window.addEventListener('resize', function() {
window.addEventListener('resize', function () {
updateImagePreviewArea();
if (selectedImages.length > 0) {
updateGridIndicator();
@ -1485,12 +1529,12 @@
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
// 顯示動畫
setTimeout(() => notification.classList.add('show'), 100);
// 自動隱藏
setTimeout(() => {
notification.classList.remove('show');
@ -1501,26 +1545,26 @@
// 處理 WebSocket 消息
function handleWebSocketMessage(data) {
console.log('收到 WebSocket 消息:', data);
switch(data.type) {
switch (data.type) {
case 'command_output':
appendCommandOutput(data.output);
break;
case 'command_finished':
handleCommandFinished(data.exit_code);
break;
case 'command_error':
appendCommandOutput(`錯誤: ${data.error}\n`);
handleCommandFinished(-1);
break;
case 'ping':
// 回應 ping
sendWebSocketMessage({ type: 'pong' });
break;
default:
console.log('未知的消息類型:', data.type);
}
@ -1543,4 +1587,5 @@
}
</script>
</body>
</html>

View File

@ -28,6 +28,7 @@ from typing import Optional, Dict, Any
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from .debug import debug_log
from .i18n import t
# 嘗試導入 Qt GUI 模組
try:
@ -42,26 +43,8 @@ def test_qt_gui():
try:
# 測試參數
project_directory = os.getcwd()
prompt = """🎯 圖片預覽和視窗調整測試
這是一個測試會話用於驗證以下功能
功能測試項目
1. 圖片上傳和預覽功能
2. 圖片右上角X刪除按鈕
3. 視窗自由調整大小
4. 分割器的靈活調整
5. 各區域的動態佈局
📋 測試步驟
1. 嘗試上傳一些圖片拖拽文件選擇剪貼板
2. 檢查圖片預覽是否正常顯示
3. 點擊圖片右上角的X按鈕刪除圖片
4. 嘗試調整視窗大小檢查是否可以自由調整
5. 拖動分割器調整各區域大小
6. 提供任何回饋或發現的問題
請測試這些功能並提供回饋"""
# 使用國際化系統獲取測試摘要
prompt = t('test.qtGuiSummary')
debug_log("🚀 啟動 Qt GUI 測試...")
debug_log("📝 測試項目:")
@ -69,6 +52,7 @@ def test_qt_gui():
debug_log(" - X刪除按鈕")
debug_log(" - 視窗大小調整")
debug_log(" - 分割器調整")
debug_log(" - 智能 Ctrl+V 功能")
debug_log("")
# 啟動 GUI

View File

@ -34,6 +34,7 @@ from typing import Dict, Any, Optional
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from .debug import debug_log
from .i18n import t
# 嘗試導入 Web UI 模組
try:
@ -43,6 +44,10 @@ except ImportError as e:
debug_log(f"⚠️ 無法導入 Web UI 模組: {e}")
WEB_UI_AVAILABLE = False
def get_test_summary():
"""獲取測試摘要,使用國際化系統"""
return t('test.webUiSummary')
def find_free_port():
"""Find a free port to use for testing"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@ -125,7 +130,8 @@ def test_web_ui(keep_running=False):
session_info = None
try:
project_dir = str(Path.cwd())
summary = "測試 Web UI 功能"
# 使用國際化系統獲取測試摘要
summary = t('test.webUiSummary')
session_id = manager.create_session(project_dir, summary)
session_info = {
'manager': manager,
@ -145,6 +151,7 @@ def test_web_ui(keep_running=False):
debug_log(" - 本地環境會繼續使用 Qt GUI")
debug_log(" - 支援即時命令執行和 WebSocket 通訊")
debug_log(" - 提供現代化的深色主題界面")
debug_log(" - 支援智能 Ctrl+V 圖片貼上功能")
return True, session_info
@ -185,8 +192,8 @@ def test_mcp_integration():
# Test timeout parameter
debug_log("✅ 支援 timeout 參數")
# Test force_web_ui parameter
debug_log("✅ 支援 force_web_ui 參數")
# Test environment-based Web UI selection
debug_log("✅ 支援基於環境變數的 Web UI 選擇")
# Test would require actual MCP call, so just verify import
debug_log("✅ 準備接受來自 AI 助手的調用")
@ -197,8 +204,8 @@ def test_mcp_integration():
return False
def test_new_parameters():
"""Test new timeout and force_web_ui parameters"""
debug_log("\n🆕 測試新增參數功能")
"""Test timeout parameter and environment variable support"""
debug_log("\n🆕 測試參數功能")
debug_log("-" * 30)
try:
@ -216,45 +223,50 @@ def test_new_parameters():
debug_log("❌ timeout 參數不存在")
return False
# 檢查 force_web_ui 參數
if 'force_web_ui' in sig.parameters:
force_web_ui_param = sig.parameters['force_web_ui']
debug_log(f"✅ force_web_ui 參數存在,預設值: {force_web_ui_param.default}")
# 檢查環境變數支援
import os
current_force_web = os.getenv("FORCE_WEB")
if current_force_web:
debug_log(f"✅ 檢測到 FORCE_WEB 環境變數: {current_force_web}")
else:
debug_log("❌ force_web_ui 參數不存在")
return False
debug_log(" FORCE_WEB 環境變數未設定(將使用預設邏輯)")
debug_log("所有新參數功能正常")
debug_log("參數功能正常")
return True
except Exception as e:
debug_log(f"參數測試失敗: {e}")
debug_log(f"參數測試失敗: {e}")
return False
def test_force_web_ui_mode():
"""Test force web UI mode"""
debug_log("\n🌐 測試制 Web UI 模式")
def test_environment_web_ui_mode():
"""Test environment-based Web UI mode"""
debug_log("\n🌐 測試環境變數控制 Web UI 模式")
debug_log("-" * 30)
try:
from .server import interactive_feedback, is_remote_environment, can_use_gui
import os
# 顯示當前環境狀態
is_remote = is_remote_environment()
gui_available = can_use_gui()
force_web_env = os.getenv("FORCE_WEB", "").lower()
debug_log(f"當前環境 - 遠端: {is_remote}, GUI 可用: {gui_available}")
debug_log(f"FORCE_WEB 環境變數: {force_web_env or '未設定'}")
if not is_remote and gui_available:
debug_log("✅ 在本地 GUI 環境中可以使用 force_web_ui=True 強制使用 Web UI")
debug_log("💡 這對於測試 Web UI 功能非常有用")
if force_web_env in ("true", "1", "yes", "on"):
debug_log("✅ FORCE_WEB 已啟用,將強制使用 Web UI")
elif not is_remote and gui_available:
debug_log(" 本地 GUI 環境,將使用 Qt GUI")
debug_log("💡 可設定 FORCE_WEB=true 強制使用 Web UI 進行測試")
else:
debug_log(" 當前環境會自動使用 Web UI")
debug_log(" 將自動使用 Web UI遠端環境或 GUI 不可用)")
return True
except Exception as e:
debug_log(f"強制 Web UI 測試失敗: {e}")
debug_log(f"環境變數測試失敗: {e}")
return False
def interactive_demo(session_info):
@ -309,8 +321,8 @@ if __name__ == "__main__":
# Test new parameters
params_test = test_new_parameters()
# Test force web UI mode
force_test = test_force_web_ui_mode()
# Test environment-based Web UI mode
env_web_test = test_environment_web_ui_mode()
# Test MCP integration
mcp_test = test_mcp_integration()
@ -319,7 +331,7 @@ if __name__ == "__main__":
web_test, session_info = test_web_ui()
debug_log("\n" + "=" * 60)
if env_test and params_test and force_test and mcp_test and web_test:
if env_test and params_test and env_web_test and mcp_test and web_test:
debug_log("🎊 所有測試完成!準備使用 Interactive Feedback MCP")
debug_log("\n📖 使用方法:")
debug_log(" 1. 在 Cursor/Cline 中配置此 MCP 服務器")

View File

@ -298,6 +298,31 @@ class WebUIManager:
else:
return HTMLResponse(self._get_simple_feedback_html(session_id, session))
@self.app.get("/api/translations")
async def get_translations():
"""提供語系檔案 API"""
try:
translations = {}
locales_dir = package_dir / "locales"
if locales_dir.exists():
for lang_dir in locales_dir.iterdir():
if lang_dir.is_dir():
lang_code = lang_dir.name
translation_file = lang_dir / "translations.json"
if translation_file.exists():
try:
with open(translation_file, 'r', encoding='utf-8') as f:
translations[lang_code] = json.load(f)
except Exception as e:
debug_log(f"載入語言檔案失敗 {lang_code}: {e}")
return JSONResponse(translations)
except Exception as e:
debug_log(f"語系 API 錯誤: {e}")
return JSONResponse({}, status_code=500)
@self.app.websocket("/ws/{session_id}")
async def websocket_endpoint(websocket: WebSocket, session_id: str):
"""WebSocket 連接處理"""