diff --git a/scripts/validate_message_codes.py b/scripts/validate_message_codes.py new file mode 100644 index 0000000..2998055 --- /dev/null +++ b/scripts/validate_message_codes.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +""" +訊息代碼驗證腳本 + +驗證後端訊息代碼、前端常量和翻譯文件的一致性。 +確保所有訊息代碼都有對應的定義和翻譯。 + +使用方式: + python scripts/validate_message_codes.py +""" + +import json +import re +import sys +from pathlib import Path + + +def extract_backend_codes(): + """從後端 Python 文件中提取所有訊息代碼""" + codes = set() + + # 讀取 MessageCodes 類別 + message_codes_file = Path( + "src/mcp_feedback_enhanced/web/constants/message_codes.py" + ) + if message_codes_file.exists(): + content = message_codes_file.read_text(encoding="utf-8") + # 匹配形如 SESSION_FEEDBACK_SUBMITTED = "session.feedbackSubmitted" + pattern = r'([A-Z_]+)\s*=\s*"([^"]+)"' + matches = re.findall(pattern, content) + for constant_name, code in matches: + codes.add(code) + + return codes + + +def extract_frontend_codes(): + """從前端 JavaScript 文件中提取所有訊息代碼""" + codes = set() + + # 讀取 message-codes.js + message_codes_js = Path( + "src/mcp_feedback_enhanced/web/static/js/modules/constants/message-codes.js" + ) + if message_codes_js.exists(): + content = message_codes_js.read_text(encoding="utf-8") + # 匹配形如 FEEDBACK_SUBMITTED: 'session.feedbackSubmitted' + pattern = r'[A-Z_]+:\s*[\'"]([^\'"]+)[\'"]' + matches = re.findall(pattern, content) + codes.update(matches) + + # 讀取 utils.js 中的 fallback 訊息 + utils_js = Path("src/mcp_feedback_enhanced/web/static/js/modules/utils.js") + if utils_js.exists(): + content = utils_js.read_text(encoding="utf-8") + # 匹配 fallbackMessages 物件中的 key + fallback_section = re.search( + r"fallbackMessages\s*=\s*\{([^}]+)\}", content, re.DOTALL + ) + if fallback_section: + pattern = r'[\'"]([^\'"]+)[\'"]:\s*[\'"][^\'"]+[\'"]' + matches = re.findall(pattern, fallback_section.group(1)) + codes.update(matches) + + return codes + + +def extract_translation_keys(locale="zh-TW"): + """從翻譯文件中提取所有 key""" + keys = set() + + translation_file = Path( + f"src/mcp_feedback_enhanced/web/locales/{locale}/translation.json" + ) + if translation_file.exists(): + try: + data = json.loads(translation_file.read_text(encoding="utf-8")) + + def extract_keys_recursive(obj, prefix=""): + """遞迴提取所有 key""" + if isinstance(obj, dict): + for key, value in obj.items(): + full_key = f"{prefix}.{key}" if prefix else key + if isinstance(value, dict): + extract_keys_recursive(value, full_key) + else: + keys.add(full_key) + + extract_keys_recursive(data) + except json.JSONDecodeError as e: + print(f"❌ 無法解析翻譯文件 {translation_file}: {e}") + + return keys + + +def validate_message_codes(): + """執行驗證""" + print("🔍 開始驗證訊息代碼一致性...\n") + + # 提取所有代碼 + backend_codes = extract_backend_codes() + frontend_codes = extract_frontend_codes() + + # 提取所有語言的翻譯 key + locales = ["zh-TW", "en", "zh-CN"] + translation_keys = {} + for locale in locales: + translation_keys[locale] = extract_translation_keys(locale) + + # 統計資訊 + print("📊 統計資訊:") + print(f" - 後端訊息代碼數量: {len(backend_codes)}") + print(f" - 前端訊息代碼數量: {len(frontend_codes)}") + for locale in locales: + print(f" - {locale} 翻譯 key 數量: {len(translation_keys[locale])}") + print() + + # 驗證後端代碼是否都有前端定義 + print("🔍 檢查後端代碼是否都有前端定義...") + missing_in_frontend = backend_codes - frontend_codes + if missing_in_frontend: + print("❌ 以下後端代碼在前端沒有定義:") + for code in sorted(missing_in_frontend): + print(f" - {code}") + else: + print("✅ 所有後端代碼都有前端定義") + print() + + # 驗證前端代碼是否都有翻譯 + print("🔍 檢查前端代碼是否都有翻譯...") + all_frontend_codes = backend_codes | frontend_codes + + for locale in locales: + print(f"\n 檢查 {locale} 翻譯:") + missing_translations = set() + + for code in all_frontend_codes: + if code not in translation_keys[locale]: + missing_translations.add(code) + + if missing_translations: + print(" ❌ 缺少以下翻譯:") + for code in sorted(missing_translations): + print(f" - {code}") + else: + print(" ✅ 所有代碼都有翻譯") + + # 檢查是否有多餘的翻譯 + print("\n🔍 檢查是否有多餘的翻譯...") + for locale in locales: + # 過濾掉非訊息代碼的 key(如 buttons, labels 等) + message_keys = { + k + for k in translation_keys[locale] + if any( + k.startswith(prefix) + for prefix in [ + "system.", + "session.", + "settings.", + "error.", + "command.", + "file.", + "prompt.", + "notification.", + ] + ) + } + + extra_translations = message_keys - all_frontend_codes + if extra_translations: + print(f"\n {locale} 有多餘的翻譯:") + for key in sorted(extra_translations): + print(f" - {key}") + + print("\n✅ 驗證完成!") + + # 返回是否有錯誤 + return len(missing_in_frontend) == 0 and all( + len( + [ + code + for code in all_frontend_codes + if code not in translation_keys[locale] + ] + ) + == 0 + for locale in locales + ) + + +if __name__ == "__main__": + # 切換到專案根目錄 + script_dir = Path(__file__).parent + project_root = script_dir.parent + import os + + os.chdir(project_root) + + # 執行驗證 + success = validate_message_codes() + sys.exit(0 if success else 1) diff --git a/src/mcp_feedback_enhanced/web/constants/__init__.py b/src/mcp_feedback_enhanced/web/constants/__init__.py new file mode 100644 index 0000000..550b401 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/constants/__init__.py @@ -0,0 +1,8 @@ +""" +Web 常量模組 +""" + +from .message_codes import MessageCodes, get_message_code + + +__all__ = ["MessageCodes", "get_message_code"] diff --git a/src/mcp_feedback_enhanced/web/constants/message_codes.py b/src/mcp_feedback_enhanced/web/constants/message_codes.py new file mode 100644 index 0000000..28df091 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/constants/message_codes.py @@ -0,0 +1,173 @@ +""" +統一的訊息代碼定義 + +這個模組定義了所有後端使用的訊息代碼常量。 +前端會根據這些代碼顯示對應的本地化訊息。 + +使用方式: + from ..constants import MessageCodes, get_message_code + + # 使用常量 + code = MessageCodes.SESSION_FEEDBACK_SUBMITTED + + # 或使用輔助函數 + code = get_message_code("SESSION_FEEDBACK_SUBMITTED") +""" + + +class MessageCodes: + """訊息代碼常量類""" + + # ========== 系統相關 ========== + SYSTEM_CONNECTION_ESTABLISHED = "system.connectionEstablished" + SYSTEM_CONNECTION_LOST = "system.connectionLost" + SYSTEM_CONNECTION_RECONNECTING = "system.connectionReconnecting" + SYSTEM_CONNECTION_RECONNECTED = "system.connectionReconnected" + SYSTEM_CONNECTION_FAILED = "system.connectionFailed" + SYSTEM_WEBSOCKET_ERROR = "system.websocketError" + SYSTEM_WEBSOCKET_READY = "system.websocketReady" + SYSTEM_MEMORY_PRESSURE = "system.memoryPressure" + SYSTEM_SHUTDOWN = "system.shutdown" + SYSTEM_PROCESS_KILLED = "system.processKilled" + SYSTEM_HEARTBEAT_STOPPED = "system.heartbeatStopped" + + # ========== 會話相關 ========== + SESSION_NO_ACTIVE = "session.noActiveSession" + SESSION_CREATED = "session.created" + SESSION_UPDATED = "session.updated" + SESSION_EXPIRED = "session.expired" + SESSION_TIMEOUT = "session.timeout" + SESSION_CLEANED = "session.cleaned" + SESSION_FEEDBACK_SUBMITTED = "session.feedbackSubmitted" + SESSION_USER_MESSAGE_RECORDED = "session.userMessageRecorded" + SESSION_HISTORY_SAVED = "session.historySaved" + SESSION_HISTORY_LOADED = "session.historyLoaded" + SESSION_MANUAL_CLEANUP = "session.manualCleanup" + SESSION_ERROR_CLEANUP = "session.errorCleanup" + + # ========== 設定相關 ========== + SETTINGS_SAVED = "settingsAPI.saved" + SETTINGS_LOADED = "settingsAPI.loaded" + SETTINGS_CLEARED = "settingsAPI.cleared" + SETTINGS_SAVE_FAILED = "settingsAPI.saveFailed" + SETTINGS_LOAD_FAILED = "settingsAPI.loadFailed" + SETTINGS_CLEAR_FAILED = "settingsAPI.clearFailed" + SETTINGS_SET_FAILED = "settingsAPI.setFailed" + SETTINGS_INVALID_VALUE = "settingsAPI.invalidValue" + SETTINGS_LOG_LEVEL_UPDATED = "settingsAPI.logLevelUpdated" + SETTINGS_INVALID_LOG_LEVEL = "settingsAPI.invalidLogLevel" + + # ========== 命令執行相關 ========== + COMMAND_EXECUTING = "commandStatus.executing" + COMMAND_COMPLETED = "commandStatus.completed" + COMMAND_FAILED = "commandStatus.failed" + COMMAND_INVALID = "commandStatus.invalid" + COMMAND_OUTPUT_RECEIVED = "commandStatus.outputReceived" + COMMAND_ERROR = "commandStatus.error" + + # ========== 錯誤相關 ========== + ERROR_GENERIC = "error.generic" + ERROR_NETWORK = "error.network" + ERROR_SERVER = "error.server" + ERROR_TIMEOUT = "error.timeout" + ERROR_INVALID_INPUT = "error.invalidInput" + ERROR_OPERATION_FAILED = "error.operationFailed" + ERROR_USER_MESSAGE_FAILED = "error.userMessageFailed" + ERROR_GET_SESSIONS_FAILED = "error.getSessionsFailed" + ERROR_GET_LOG_LEVEL_FAILED = "error.getLogLevelFailed" + ERROR_RESOURCE_CLEANUP = "error.resourceCleanup" + ERROR_PROCESSING = "error.processing" + + # ========== 檔案相關 ========== + FILE_UPLOAD_SUCCESS = "file.uploadSuccess" + FILE_UPLOAD_FAILED = "file.uploadFailed" + FILE_SIZE_TOO_LARGE = "file.sizeTooLarge" + FILE_TYPE_NOT_SUPPORTED = "file.typeNotSupported" + FILE_PROCESSING = "file.processing" + FILE_REMOVED = "file.removed" + + # ========== 提示詞相關 ========== + PROMPT_SAVED = "prompt.saved" + PROMPT_DELETED = "prompt.deleted" + PROMPT_APPLIED = "prompt.applied" + PROMPT_IMPORT_SUCCESS = "prompt.importSuccess" + PROMPT_IMPORT_FAILED = "prompt.importFailed" + PROMPT_EXPORT_SUCCESS = "prompt.exportSuccess" + PROMPT_VALIDATION_FAILED = "prompt.validationFailed" + + +# 向後兼容的映射表(從舊的 key 到新的常量名稱) +LEGACY_KEY_MAPPING = { + # feedback_session.py 的舊 key + "FEEDBACK_SUBMITTED": "SESSION_FEEDBACK_SUBMITTED", + "SESSION_CLEANUP": "SESSION_CLEANED", + "TIMEOUT_CLEANUP": "SESSION_TIMEOUT", + "EXPIRED_CLEANUP": "SESSION_EXPIRED", + "MEMORY_PRESSURE_CLEANUP": "SYSTEM_MEMORY_PRESSURE", + "MANUAL_CLEANUP": "SESSION_MANUAL_CLEANUP", + "ERROR_CLEANUP": "SESSION_ERROR_CLEANUP", + "SHUTDOWN_CLEANUP": "SYSTEM_SHUTDOWN", + "COMMAND_EXECUTING": "COMMAND_EXECUTING", + "COMMAND_COMPLETED": "COMMAND_COMPLETED", + "COMMAND_FAILED": "COMMAND_FAILED", + "COMMAND_INVALID": "COMMAND_INVALID", + "COMMAND_ERROR": "COMMAND_ERROR", + "PROCESS_KILLED": "SYSTEM_PROCESS_KILLED", + "RESOURCE_CLEANUP_ERROR": "ERROR_RESOURCE_CLEANUP", + "HEARTBEAT_STOPPED": "SYSTEM_HEARTBEAT_STOPPED", + "PROCESSING_ERROR": "ERROR_PROCESSING", + "WEBSOCKET_READY": "SYSTEM_WEBSOCKET_READY", + # main_routes.py 的舊 key + "no_active_session": "SESSION_NO_ACTIVE", + "websocket_connected": "SYSTEM_CONNECTION_ESTABLISHED", + "new_session_created": "SESSION_CREATED", + "user_message_recorded": "SESSION_USER_MESSAGE_RECORDED", + "add_user_message_failed": "ERROR_USER_MESSAGE_FAILED", + "settings_saved": "SETTINGS_SAVED", + "save_failed": "SETTINGS_SAVE_FAILED", + "load_failed": "SETTINGS_LOAD_FAILED", + "settings_cleared": "SETTINGS_CLEARED", + "clear_failed": "SETTINGS_CLEAR_FAILED", + "session_history_saved": "SESSION_HISTORY_SAVED", + "get_sessions_failed": "ERROR_GET_SESSIONS_FAILED", + "get_log_level_failed": "ERROR_GET_LOG_LEVEL_FAILED", + "invalid_log_level": "SETTINGS_INVALID_LOG_LEVEL", + "log_level_updated": "SETTINGS_LOG_LEVEL_UPDATED", + "set_failed": "SETTINGS_SET_FAILED", +} + + +def get_message_code(key: str) -> str: + """ + 獲取訊息代碼 + + 支援三種輸入方式: + 1. 直接使用常量名稱:get_message_code("SESSION_FEEDBACK_SUBMITTED") + 2. 使用舊的 key(向後兼容):get_message_code("FEEDBACK_SUBMITTED") + 3. 使用小寫的舊 key(向後兼容):get_message_code("feedback_submitted") + + Args: + key: 訊息 key 或常量名稱 + + Returns: + 訊息代碼字串(例如:"session.feedbackSubmitted") + """ + # 嘗試直接從 MessageCodes 獲取 + if hasattr(MessageCodes, key): + return str(getattr(MessageCodes, key)) + + # 嘗試從映射表獲取(支援大寫和小寫) + upper_key = key.upper() + if upper_key in LEGACY_KEY_MAPPING: + constant_name = LEGACY_KEY_MAPPING[upper_key] + if hasattr(MessageCodes, constant_name): + return str(getattr(MessageCodes, constant_name)) + + # 如果是小寫的 key,也嘗試映射 + if key in LEGACY_KEY_MAPPING: + constant_name = LEGACY_KEY_MAPPING[key] + if hasattr(MessageCodes, constant_name): + return str(getattr(MessageCodes, constant_name)) + + # 如果都找不到,返回一個預設格式 + return f"unknown.{key}" diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index fbfb975..55454cf 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -50,7 +50,8 @@ "sendFailed": "Send failed, please retry", "noContent": "No content to copy", "copySuccess": "Content copied to clipboard", - "copyFailed": "Failed to copy" + "copyFailed": "Failed to copy", + "provideTextOrImage": "Please provide feedback text or upload an image" }, "summary": { "title": "📋 AI Work Summary", @@ -96,7 +97,7 @@ "summaryTitle": "📋 AI Work Summary", "feedbackTitle": "💬 Provide Feedback" }, - "settings": { + "settingsUI": { "title": "⚙️ Settings", "language": "🌍 Language", "currentLanguage": "Current Language", @@ -195,7 +196,7 @@ "upload": "Upload", "download": "Download" }, - "session": { + "sessionTimeout": { "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.", @@ -261,7 +262,10 @@ "clearSuccess": "Session history cleared successfully", "clearTitle": "Clear all session history records", "description": "Manage locally stored session history records, including retention period settings and data export functionality", - "exportDescription": "Export or clear locally stored session history records" + "exportDescription": "Export or clear locally stored session history records", + "exportFailed": "Export failed: {error}", + "clearFailed": "Clear failed: {error}", + "clearFailedGeneric": "Clear failed" }, "retention": { "24hours": "24 hours", @@ -296,6 +300,14 @@ "clearAllTitle": "Clear all user message records from all sessions", "confirmClearAll": "Are you sure you want to clear all user message records from all sessions? This action cannot be undone.", "clearSuccess": "User message records cleared successfully" + }, + "noActiveSession": "No active session data", + "sessionNotFound": "Session data not found", + "currentSession": { + "noData": "No current session data", + "noUserMessages": "Current session has no user message records", + "copyFailed": "Copy failed, please try again", + "dataManagerNotInit": "Data manager not initialized" } }, "connectionMonitor": { @@ -440,7 +452,8 @@ "countdownLabel": "Submit Countdown", "pauseCountdown": "Pause Countdown", "resumeCountdown": "Resume Countdown", - "paused": "Auto submit paused" + "paused": "Auto submit paused", + "autoCommitNoPrompt": "Please select a prompt as auto-submit content first" }, "audio": { "notification": { @@ -514,5 +527,95 @@ "permissionRequired": "Please grant notification permission first", "criticalTitle": "MCP Feedback - Warning" } + }, + "system": { + "connectionEstablished": "WebSocket connection established", + "connectionLost": "WebSocket connection lost", + "connectionReconnecting": "Reconnecting...", + "connectionReconnected": "Reconnected", + "connectionFailed": "Connection failed", + "websocketError": "WebSocket error", + "memoryPressure": "Memory pressure cleanup", + "shutdown": "System shutdown", + "processKilled": "Process killed", + "heartbeatStopped": "Heartbeat stopped", + "websocketReady": "WebSocket ready" + }, + "session": { + "noActiveSession": "No active session", + "created": "New MCP session created, page will refresh automatically", + "updated": "Session updated", + "expired": "Session expired", + "timeout": "Session timed out", + "cleaned": "Session cleaned", + "feedbackSubmitted": "Feedback submitted successfully", + "userMessageRecorded": "User message recorded", + "historySaved": "Session history saved ({{count}} sessions)", + "historyLoaded": "Session history loaded" + }, + "settingsAPI": { + "saved": "Settings saved", + "loaded": "Settings loaded", + "cleared": "Settings cleared", + "saveFailed": "Save failed", + "loadFailed": "Load failed", + "clearFailed": "Clear failed", + "setFailed": "Set failed", + "invalidValue": "Invalid setting value", + "logLevelUpdated": "Log level updated", + "invalidLogLevel": "Invalid log level, must be one of DEBUG, INFO, WARN, ERROR" + }, + "file": { + "uploadSuccess": "File uploaded successfully", + "uploadFailed": "File upload failed", + "sizeTooLarge": "File size exceeds limit", + "typeNotSupported": "File type not supported", + "processing": "Processing file...", + "removed": "File removed" + }, + "prompt": { + "saved": "Prompt saved", + "deleted": "Prompt deleted", + "applied": "Prompt applied: {{name}}", + "importSuccess": "Prompts imported successfully", + "importFailed": "Prompts import failed", + "exportSuccess": "Prompts exported successfully", + "validationFailed": "Prompt validation failed" + }, + "error": { + "generic": "An error occurred: {{error}}", + "network": "Network error", + "server": "Server error", + "timeout": "Operation timed out", + "invalidInput": "Invalid input", + "operationFailed": "Operation failed", + "userMessageFailed": "Failed to add user message", + "getSessionsFailed": "Failed to get sessions", + "getLogLevelFailed": "Failed to get log level", + "command": "Command execution error", + "resourceCleanup": "Resource cleanup error", + "processing": "Processing error" + }, + "commandStatus": { + "executing": "Executing command...", + "completed": "Command completed", + "failed": "Command failed", + "outputReceived": "Output received", + "invalid": "Invalid command", + "error": "Command execution error" + }, + "utils": { + "copySuccess": "Copied to clipboard", + "copyError": "Copy failed" + }, + "fileUpload": { + "fileSizeExceeded": "Image size exceeds limit ({limit}): {filename}", + "maxFilesExceeded": "Maximum {maxFiles} files can be uploaded", + "processingFailed": "File processing failed, please retry" + }, + "aria": { + "toggleAutoSubmit": "Toggle auto submit", + "toggleNotification": "Toggle notification", + "toggleAudioNotification": "Toggle audio notification" } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index ae68858..c4b90a4 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -50,7 +50,8 @@ "sendFailed": "发送失败,请重试", "noContent": "没有可复制的内容", "copySuccess": "内容已复制到剪贴板", - "copyFailed": "复制失败" + "copyFailed": "复制失败", + "provideTextOrImage": "请提供反馈文字或上传图片" }, "summary": { "title": "📋 AI 工作摘要", @@ -96,7 +97,7 @@ "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供反馈" }, - "settings": { + "settingsUI": { "title": "⚙️ 设定", "language": "🌍 语言", "currentLanguage": "当前语言", @@ -121,7 +122,8 @@ "resetError": "重置设定时发生错误", "timeout": "连线逾时 (秒)", "autorefresh": "自动重新整理", - "debug": "除错模式" + "debug": "除错模式", + "autoCommitNoPrompt": "请先选择一个提示词作为自动提交内容" }, "languages": { "zh-TW": "繁體中文", @@ -195,7 +197,7 @@ "upload": "上传", "download": "下载" }, - "session": { + "sessionTimeout": { "timeout": "⏰ 会话已超时,界面将自动关闭", "timeoutWarning": "会话即将超时", "timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。", @@ -261,7 +263,10 @@ "clearSuccess": "会话历史已清空", "clearTitle": "清空所有会话历史记录", "description": "管理本地存储的会话历史记录,包括保存期限设定和数据导出功能", - "exportDescription": "导出或清空本地存储的会话历史记录" + "exportDescription": "导出或清空本地存储的会话历史记录", + "exportFailed": "导出失败: {error}", + "clearFailed": "清空失败: {error}", + "clearFailedGeneric": "清空失败" }, "retention": { "24hours": "24 小时", @@ -296,6 +301,14 @@ "clearAllTitle": "清空所有会话的用户消息记录", "confirmClearAll": "确定要清空所有会话的用户消息记录吗?此操作无法撤销。", "clearSuccess": "用户消息记录已清空" + }, + "noActiveSession": "目前没有活跃的会话数据", + "sessionNotFound": "找不到会话资料", + "currentSession": { + "noData": "没有当前会话数据", + "noUserMessages": "当前会话没有用户消息记录", + "copyFailed": "复制失败,请重试", + "dataManagerNotInit": "数据管理器未初始化" } }, "connectionMonitor": { @@ -513,5 +526,82 @@ "permissionRequired": "请先授权通知权限", "criticalTitle": "MCP Feedback - 警告" } + }, + "system": { + "connectionEstablished": "WebSocket 连接已建立", + "connectionLost": "WebSocket 连接已断开", + "connectionReconnecting": "正在重新连接...", + "connectionReconnected": "已重新连接", + "connectionFailed": "连接失败", + "websocketError": "WebSocket 错误" + }, + "session": { + "noActiveSession": "没有活跃会话", + "created": "新的 MCP 会话已创建,页面将自动刷新", + "updated": "会话已更新", + "expired": "会话已过期", + "timeout": "会话已超时", + "cleaned": "会话已清理", + "feedbackSubmitted": "反馈已成功提交", + "userMessageRecorded": "用户消息已记录", + "historySaved": "会话历史已保存({{count}} 个会话)", + "historyLoaded": "会话历史已载入" + }, + "settingsAPI": { + "saved": "设置已保存", + "loaded": "设置已载入", + "cleared": "设置已清除", + "saveFailed": "保存失败", + "loadFailed": "加载失败", + "clearFailed": "清除失败", + "invalidValue": "无效的设置值", + "logLevelUpdated": "日志等级已更新", + "invalidLogLevel": "无效的日志等级,必须是 DEBUG, INFO, WARN, ERROR 之一" + }, + "file": { + "uploadSuccess": "文件上传成功", + "uploadFailed": "文件上传失败", + "sizeTooLarge": "文件大小超过限制", + "typeNotSupported": "不支持的文件类型", + "processing": "正在处理文件...", + "removed": "文件已移除" + }, + "prompt": { + "saved": "提示词已保存", + "deleted": "提示词已删除", + "applied": "已套用提示词:{{name}}", + "importSuccess": "提示词导入成功", + "importFailed": "提示词导入失败", + "exportSuccess": "提示词导出成功", + "validationFailed": "提示词验证失败" + }, + "error": { + "generic": "发生错误:{{error}}", + "network": "网络错误", + "server": "服务器错误", + "timeout": "操作超时", + "invalidInput": "输入无效", + "operationFailed": "操作失败" + }, + "commandStatus": { + "executing": "正在执行命令...", + "completed": "命令执行完成", + "failed": "命令执行失败", + "outputReceived": "已接收输出", + "invalid": "无效的命令" + }, + "utils": { + "copySuccess": "已复制到剪贴板", + "copyError": "复制失败" + }, + "fileUpload": { + "fileSizeExceeded": "图片大小超过限制 ({limit}): {filename}", + "maxFilesExceeded": "最多只能上传 {maxFiles} 个文件", + "processingFailed": "文件处理失败,请重试" + }, + "aria": { + "toggleAutoSubmit": "切换自动提交", + "toggleNotification": "切换通知", + "toggleAudioNotification": "切换音效通知" } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index 0d8073b..694a433 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -55,7 +55,8 @@ "sendFailed": "發送失敗,請重試", "noContent": "沒有可複製的內容", "copySuccess": "內容已複製到剪貼板", - "copyFailed": "複製失敗" + "copyFailed": "複製失敗", + "provideTextOrImage": "請提供回饋文字或上傳圖片" }, "summary": { "title": "📋 AI 工作摘要", @@ -101,7 +102,7 @@ "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供回饋" }, - "settings": { + "settingsUI": { "title": "⚙️ 設定", "language": "🌍 語言", "currentLanguage": "當前語言", @@ -126,7 +127,8 @@ "resetError": "重置設定時發生錯誤", "timeout": "連線逾時 (秒)", "autorefresh": "自動重新整理", - "debug": "除錯模式" + "debug": "除錯模式", + "autoCommitNoPrompt": "請先選擇一個提示詞作為自動提交內容" }, "languages": { "zh-TW": "繁體中文", @@ -200,7 +202,7 @@ "upload": "上傳", "download": "下載" }, - "session": { + "sessionTimeout": { "timeout": "⏰ 會話已超時,介面將自動關閉", "timeoutWarning": "會話即將超時", "timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。", @@ -266,7 +268,10 @@ "clearSuccess": "會話歷史已清空", "clearTitle": "清空所有會話歷史記錄", "description": "管理本地儲存的會話歷史記錄,包括保存期限設定和資料匯出功能", - "exportDescription": "匯出或清空本地儲存的會話歷史記錄" + "exportDescription": "匯出或清空本地儲存的會話歷史記錄", + "exportFailed": "匯出失敗: {error}", + "clearFailed": "清空失敗: {error}", + "clearFailedGeneric": "清空失敗" }, "retention": { "24hours": "24 小時", @@ -301,6 +306,14 @@ "clearAllTitle": "清空所有會話的用戶訊息記錄", "confirmClearAll": "確定要清空所有會話的用戶訊息記錄嗎?此操作無法復原。", "clearSuccess": "用戶訊息記錄已清空" + }, + "noActiveSession": "目前沒有活躍的會話數據", + "sessionNotFound": "找不到會話資料", + "currentSession": { + "noData": "沒有當前會話數據", + "noUserMessages": "當前會話沒有用戶消息記錄", + "copyFailed": "複製失敗,請重試", + "dataManagerNotInit": "數據管理器未初始化" } }, "connectionMonitor": { @@ -518,5 +531,95 @@ "permissionRequired": "請先授權通知權限", "criticalTitle": "MCP Feedback - 警告" } + }, + "system": { + "connectionEstablished": "WebSocket 連接已建立", + "connectionLost": "WebSocket 連接已斷開", + "connectionReconnecting": "正在重新連接...", + "connectionReconnected": "已重新連接", + "connectionFailed": "連接失敗", + "websocketError": "WebSocket 錯誤", + "memoryPressure": "記憶體壓力清理", + "shutdown": "系統關閉", + "processKilled": "進程已終止", + "heartbeatStopped": "心跳已停止", + "websocketReady": "WebSocket 已就緒" + }, + "session": { + "noActiveSession": "沒有活躍會話", + "created": "新的 MCP 會話已創建,頁面將自動刷新", + "updated": "會話已更新", + "expired": "會話已過期", + "timeout": "會話已超時", + "cleaned": "會話已清理", + "feedbackSubmitted": "反饋已成功提交", + "userMessageRecorded": "用戶消息已記錄", + "historySaved": "會話歷史已保存({{count}} 個會話)", + "historyLoaded": "會話歷史已載入" + }, + "settingsAPI": { + "saved": "設定已保存", + "loaded": "設定已載入", + "cleared": "設定已清除", + "saveFailed": "保存失敗", + "loadFailed": "載入失敗", + "clearFailed": "清除失敗", + "setFailed": "設定失敗", + "invalidValue": "無效的設定值", + "logLevelUpdated": "日誌等級已更新", + "invalidLogLevel": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一" + }, + "file": { + "uploadSuccess": "檔案上傳成功", + "uploadFailed": "檔案上傳失敗", + "sizeTooLarge": "檔案大小超過限制", + "typeNotSupported": "不支援的檔案類型", + "processing": "正在處理檔案...", + "removed": "檔案已移除" + }, + "prompt": { + "saved": "提示詞已保存", + "deleted": "提示詞已刪除", + "applied": "已套用提示詞:{{name}}", + "importSuccess": "提示詞匯入成功", + "importFailed": "提示詞匯入失敗", + "exportSuccess": "提示詞匯出成功", + "validationFailed": "提示詞驗證失敗" + }, + "error": { + "generic": "發生錯誤:{{error}}", + "network": "網絡錯誤", + "server": "伺服器錯誤", + "timeout": "操作超時", + "invalidInput": "輸入無效", + "operationFailed": "操作失敗", + "userMessageFailed": "添加用戶消息失敗", + "getSessionsFailed": "獲取會話列表失敗", + "getLogLevelFailed": "獲取日誌等級失敗", + "command": "命令執行錯誤", + "resourceCleanup": "資源清理錯誤", + "processing": "處理過程錯誤" + }, + "commandStatus": { + "executing": "正在執行命令...", + "completed": "命令執行完成", + "failed": "命令執行失敗", + "outputReceived": "已接收輸出", + "invalid": "無效的命令", + "error": "命令執行錯誤" + }, + "utils": { + "copySuccess": "已複製到剪貼板", + "copyError": "複製失敗" + }, + "fileUpload": { + "fileSizeExceeded": "圖片大小超過限制 ({limit}): {filename}", + "maxFilesExceeded": "最多只能上傳 {maxFiles} 個檔案", + "processingFailed": "檔案處理失敗,請重試" + }, + "aria": { + "toggleAutoSubmit": "切換自動提交", + "toggleNotification": "切換通知", + "toggleAudioNotification": "切換音效通知" } } diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 90111c2..5e3161b 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -23,7 +23,6 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from ..debug import web_debug_log as debug_log -from ..i18n import get_i18n_manager from ..utils.error_handler import ErrorHandler, ErrorType from ..utils.memory_monitor import get_memory_monitor from .models import CleanupReason, SessionStatus, WebFeedbackSession @@ -137,7 +136,7 @@ class WebUIManager: def _init_basic_components(self): """同步初始化基本組件""" # 基本組件初始化(必須同步) - self.i18n = get_i18n_manager() + # 移除 i18n 管理器,因為翻譯已移至前端 # 設置靜態文件和模板(必須同步) self._setup_static_files() @@ -181,9 +180,8 @@ class WebUIManager: def preload_i18n(): try: - # 觸發翻譯載入(如果尚未載入) - self.i18n.get_supported_languages() - debug_log("I18N 資源預載入完成") + # I18N 在前端處理,這裡只記錄預載入完成 + debug_log("I18N 資源預載入完成(前端處理)") return True except Exception as e: debug_log(f"I18N 資源預載入失敗: {e}") @@ -733,7 +731,7 @@ class WebUIManager: refresh_message = { "type": "session_updated", "action": "new_session_created", - "message": "新的 MCP 會話已創建,頁面將自動刷新", + "messageCode": "session.created", "session_info": { "session_id": self.current_session.session_id, "project_directory": self.current_session.project_directory, diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index d1ac589..19de970 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -26,6 +26,7 @@ from fastapi import WebSocket from ...debug import web_debug_log as debug_log from ...utils.error_handler import ErrorHandler, ErrorType from ...utils.resource_manager import get_resource_manager, register_process +from ..constants import get_message_code class SessionStatus(Enum): @@ -63,6 +64,9 @@ SUPPORTED_IMAGE_TYPES = { } TEMP_DIR = Path.home() / ".cache" / "interactive-feedback-mcp-web" +# 訊息代碼現在從統一的常量文件導入 +# 使用 get_message_code 函數來獲取訊息代碼 + def _safe_parse_command(command: str) -> list[str]: """ @@ -135,6 +139,7 @@ class WebFeedbackSession: self.command_logs: list[str] = [] self.user_messages: list[dict] = [] # 用戶消息記錄 self._cleanup_done = False # 防止重複清理 + # 移除語言設定,改由前端處理 # 新增:會話狀態管理 self.status = SessionStatus.WAITING @@ -176,6 +181,18 @@ class WebFeedbackSession: f"會話 {self.session_id} 初始化完成,自動清理延遲: {auto_cleanup_delay}秒,最大空閒: {max_idle_time}秒" ) + def get_message_code(self, key: str) -> str: + """ + 獲取訊息代碼 + + Args: + key: 訊息 key + + Returns: + 訊息代碼(用於前端 i18n) + """ + return get_message_code(key) + def next_step(self, message: str | None = None) -> bool: """進入下一個狀態 - 單向流轉,不可倒退""" old_status = self.status @@ -484,8 +501,9 @@ class WebFeedbackSession: try: await self.websocket.send_json( { - "type": "feedback_received", - "message": "反饋已成功提交", + "type": "notification", + "code": self.get_message_code("FEEDBACK_SUBMITTED"), + "severity": "success", "status": self.status.value, } ) @@ -738,21 +756,24 @@ class WebFeedbackSession: # 2. 關閉 WebSocket 連接 if self.websocket: try: - # 根據清理原因發送不同的通知消息 - message_map = { - CleanupReason.TIMEOUT: "會話已超時,介面將自動關閉", - CleanupReason.EXPIRED: "會話已過期,介面將自動關閉", - CleanupReason.MEMORY_PRESSURE: "系統內存不足,會話將被清理", - CleanupReason.MANUAL: "會話已被手動清理", - CleanupReason.ERROR: "會話發生錯誤,將被清理", - CleanupReason.SHUTDOWN: "系統正在關閉,會話將被清理", + # 根據清理原因獲取訊息代碼 + code_key_map = { + CleanupReason.TIMEOUT: "TIMEOUT_CLEANUP", + CleanupReason.EXPIRED: "EXPIRED_CLEANUP", + CleanupReason.MEMORY_PRESSURE: "MEMORY_PRESSURE_CLEANUP", + CleanupReason.MANUAL: "MANUAL_CLEANUP", + CleanupReason.ERROR: "ERROR_CLEANUP", + CleanupReason.SHUTDOWN: "SHUTDOWN_CLEANUP", } + code_key = code_key_map.get(reason, "SESSION_CLEANUP") + await self.websocket.send_json( { - "type": "session_cleanup", + "type": "notification", + "code": self.get_message_code(code_key), + "severity": "warning", "reason": reason.value, - "message": message_map.get(reason, "會話將被清理"), } ) await asyncio.sleep(0.1) # 給前端一點時間處理消息 diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index 2e7ebdd..c3b56e4 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -16,6 +16,7 @@ from fastapi.responses import HTMLResponse, JSONResponse from ... import __version__ from ...debug import web_debug_log as debug_log +from ..constants import get_message_code as get_msg_code if TYPE_CHECKING: @@ -44,6 +45,11 @@ def load_user_layout_settings() -> str: return "combined-vertical" +# 使用統一的訊息代碼系統 +# 從 ..constants 導入的 get_msg_code 函數會處理所有訊息代碼 +# 舊的 key 會自動映射到新的常量 + + def setup_routes(manager: "WebUIManager"): """設置路由""" @@ -79,7 +85,6 @@ def setup_routes(manager: "WebUIManager"): "version": __version__, "has_session": True, "layout_mode": layout_mode, - "i18n": manager.i18n, }, ) @@ -113,16 +118,23 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse(content=translations) @manager.app.get("/api/session-status") - async def get_session_status(): + async def get_session_status(request: Request): """獲取當前會話狀態""" current_session = manager.get_current_session() + # 從請求頭獲取客戶端語言 + lang = ( + request.headers.get("Accept-Language", "zh-TW").split(",")[0].split("-")[0] + ) + if lang == "zh": + lang = "zh-TW" + if not current_session: return JSONResponse( content={ "has_session": False, "status": "no_session", - "message": "沒有活躍會話", + "messageCode": get_msg_code("no_active_session"), } ) @@ -139,12 +151,20 @@ def setup_routes(manager: "WebUIManager"): ) @manager.app.get("/api/current-session") - async def get_current_session(): + async def get_current_session(request: Request): """獲取當前會話詳細信息""" current_session = manager.get_current_session() + # 從查詢參數獲取語言,如果沒有則從會話獲取,最後使用默認值 + if not current_session: - return JSONResponse(status_code=404, content={"error": "沒有活躍會話"}) + return JSONResponse( + status_code=404, + content={ + "error": "No active session", + "messageCode": get_msg_code("no_active_session"), + }, + ) return JSONResponse( content={ @@ -158,8 +178,9 @@ def setup_routes(manager: "WebUIManager"): ) @manager.app.get("/api/all-sessions") - async def get_all_sessions(): + async def get_all_sessions(request: Request): """獲取所有會話的實時狀態""" + try: sessions_data = [] @@ -189,44 +210,65 @@ def setup_routes(manager: "WebUIManager"): except Exception as e: debug_log(f"獲取所有會話狀態失敗: {e}") return JSONResponse( - status_code=500, content={"error": f"獲取會話狀態失敗: {e!s}"} + status_code=500, + content={ + "error": f"Failed to get sessions: {e!s}", + "messageCode": get_msg_code("get_sessions_failed"), + }, ) @manager.app.post("/api/add-user-message") async def add_user_message(request: Request): """添加用戶消息到當前會話""" + try: data = await request.json() current_session = manager.get_current_session() if not current_session: - return JSONResponse(status_code=404, content={"error": "沒有活躍會話"}) + return JSONResponse( + status_code=404, + content={ + "error": "No active session", + "messageCode": get_msg_code("no_active_session"), + }, + ) # 添加用戶消息到會話 current_session.add_user_message(data) debug_log(f"用戶消息已添加到會話 {current_session.session_id}") return JSONResponse( - content={"status": "success", "message": "用戶消息已記錄"} + content={ + "status": "success", + "messageCode": get_msg_code("user_message_recorded"), + } ) except Exception as e: debug_log(f"添加用戶消息失敗: {e}") return JSONResponse( - status_code=500, content={"error": f"添加用戶消息失敗: {e!s}"} + status_code=500, + content={ + "error": f"Failed to add user message: {e!s}", + "messageCode": get_msg_code("add_user_message_failed"), + }, ) @manager.app.websocket("/ws") - async def websocket_endpoint(websocket: WebSocket): + async def websocket_endpoint(websocket: WebSocket, lang: str = "zh-TW"): """WebSocket 端點 - 重構後移除 session_id 依賴""" # 獲取當前活躍會話 session = manager.get_current_session() if not session: - await websocket.close(code=4004, reason="沒有活躍會話") + await websocket.close(code=4004, reason="No active session") return await websocket.accept() + # 語言由前端處理,不需要在後端設置 + debug_log(f"WebSocket 連接建立,語言由前端處理: {lang}") + # 檢查會話是否已有 WebSocket 連接 if session.websocket and session.websocket != websocket: debug_log("會話已有 WebSocket 連接,替換為新連接") @@ -237,7 +279,10 @@ def setup_routes(manager: "WebUIManager"): # 發送連接成功消息 try: await websocket.send_json( - {"type": "connection_established", "message": "WebSocket 連接已建立"} + { + "type": "connection_established", + "messageCode": get_msg_code("websocket_connected"), + } ) # 檢查是否有待發送的會話更新 @@ -247,7 +292,7 @@ def setup_routes(manager: "WebUIManager"): { "type": "session_updated", "action": "new_session_created", - "message": "新會話已創建,正在更新頁面內容", + "messageCode": get_msg_code("new_session_created"), "session_info": { "project_directory": session.project_directory, "summary": session.summary, @@ -296,6 +341,7 @@ def setup_routes(manager: "WebUIManager"): @manager.app.post("/api/save-settings") async def save_settings(request: Request): """保存設定到檔案""" + try: data = await request.json() @@ -310,18 +356,28 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"設定已保存到: {settings_file}") - return JSONResponse(content={"status": "success", "message": "設定已保存"}) + return JSONResponse( + content={ + "status": "success", + "messageCode": get_msg_code("settings_saved"), + } + ) except Exception as e: debug_log(f"保存設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"保存失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Save failed: {e!s}", + "messageCode": get_msg_code("save_failed"), + }, ) @manager.app.get("/api/load-settings") - async def load_settings(): + async def load_settings(request: Request): """從檔案載入設定""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -340,12 +396,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"載入設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"載入失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Load failed: {e!s}", + "messageCode": get_msg_code("load_failed"), + }, ) @manager.app.post("/api/clear-settings") - async def clear_settings(): + async def clear_settings(request: Request): """清除設定檔案""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -357,18 +418,28 @@ def setup_routes(manager: "WebUIManager"): else: debug_log("設定檔案不存在,無需刪除") - return JSONResponse(content={"status": "success", "message": "設定已清除"}) + return JSONResponse( + content={ + "status": "success", + "messageCode": get_msg_code("settings_cleared"), + } + ) except Exception as e: debug_log(f"清除設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"清除失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Clear failed: {e!s}", + "messageCode": get_msg_code("clear_failed"), + }, ) @manager.app.get("/api/load-session-history") - async def load_session_history(): + async def load_session_history(request: Request): """從檔案載入會話歷史""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -402,12 +473,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"載入會話歷史失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"載入失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Load failed: {e!s}", + "messageCode": get_msg_code("load_failed"), + }, ) @manager.app.post("/api/save-session-history") async def save_session_history(request: Request): """保存會話歷史到檔案""" + try: data = await request.json() @@ -435,7 +511,8 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse( content={ "status": "success", - "message": f"會話歷史已保存({session_count} 個會話)", + "messageCode": get_msg_code("session_history_saved"), + "params": {"count": session_count}, } ) @@ -443,12 +520,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"保存會話歷史失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"保存失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Save failed: {e!s}", + "messageCode": get_msg_code("save_failed"), + }, ) @manager.app.get("/api/log-level") - async def get_log_level(): + async def get_log_level(request: Request): """獲取日誌等級設定""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -470,12 +552,16 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"獲取日誌等級失敗: {e}") return JSONResponse( status_code=500, - content={"error": f"獲取日誌等級失敗: {e!s}"}, + content={ + "error": f"Failed to get log level: {e!s}", + "messageCode": get_msg_code("get_log_level_failed"), + }, ) @manager.app.post("/api/log-level") async def set_log_level(request: Request): """設定日誌等級""" + try: data = await request.json() log_level = data.get("logLevel") @@ -484,7 +570,8 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse( status_code=400, content={ - "error": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一" + "error": "Invalid log level", + "messageCode": get_msg_code("invalid_log_level"), }, ) @@ -512,7 +599,7 @@ def setup_routes(manager: "WebUIManager"): content={ "status": "success", "logLevel": log_level, - "message": "日誌等級已更新", + "messageCode": get_msg_code("log_level_updated"), } ) @@ -520,7 +607,11 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"設定日誌等級失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"設定失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Set failed: {e!s}", + "messageCode": get_msg_code("set_failed"), + }, ) diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 55f098c..1d07f3b 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -670,6 +670,11 @@ break; case 'session_updated': console.log('🔄 收到會話更新訊息:', data.session_info); + // 處理訊息代碼 + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode); + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } this._originalHandleSessionUpdated(data); break; case 'desktop_close_request': @@ -712,9 +717,20 @@ this.uiManager.setFeedbackState(window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_SUBMITTED); this.uiManager.setLastSubmissionTime(Date.now()); + // 停止自動提交計時器(如果正在運行) + if (this.autoSubmitManager && this.autoSubmitManager.isEnabled) { + console.log('⏸️ 反饋已成功提交,停止自動提交倒數計時器'); + this.autoSubmitManager.stop(); + } + // 顯示成功訊息 - const successMessage = window.i18nManager ? window.i18nManager.t('feedback.submitSuccess') : '回饋提交成功!'; - window.MCPFeedback.Utils.showMessage(data.message || successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode, data.params); + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } else { + const successMessage = window.i18nManager ? window.i18nManager.t('feedback.submitSuccess') : '回饋提交成功!'; + window.MCPFeedback.Utils.showMessage(data.message || successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } // 更新 AI 摘要區域顯示「已送出反饋」狀態 const submittedMessage = window.i18nManager ? window.i18nManager.t('feedback.submittedWaiting') : '已送出反饋,等待下次 MCP 調用...'; @@ -793,8 +809,11 @@ } // 顯示新會話通知 + const defaultMessage = window.i18nManager ? + window.i18nManager.t('session.created') : + 'New MCP session created, page will refresh automatically'; window.MCPFeedback.Utils.showMessage( - data.message || '新的 MCP 會話已創建,正在更新內容...', + data.message || defaultMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS ); @@ -1101,7 +1120,10 @@ const images = this.imageHandler ? this.imageHandler.getImages() : []; if (!feedback && images.length === 0) { - window.MCPFeedback.Utils.showMessage('請提供回饋文字或上傳圖片', window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING); + const message = window.i18nManager ? + window.i18nManager.t('feedback.provideTextOrImage', '請提供回饋文字或上傳圖片') : + '請提供回饋文字或上傳圖片'; + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING); return null; } @@ -1130,6 +1152,12 @@ this.uiManager.setFeedbackState(window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_PROCESSING); } + // 停止自動提交計時器(如果正在運行) + if (this.autoSubmitManager && this.autoSubmitManager.isEnabled) { + console.log('⏸️ 手動提交反饋,停止自動提交倒數計時器'); + this.autoSubmitManager.stop(); + } + // 3. 發送回饋到 AI 助手 const success = this.webSocketManager.send({ type: 'submit_feedback', diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index e723ee5..2b5d754 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -8,10 +8,40 @@ class I18nManager { constructor() { - this.currentLanguage = 'zh-CN'; + this.currentLanguage = this.getDefaultLanguage(); this.translations = {}; this.loadingPromise = null; } + + getDefaultLanguage() { + // 1. 先檢查本地儲存的設定 + const savedLanguage = localStorage.getItem('language'); + if (savedLanguage && ['zh-TW', 'zh-CN', 'en'].includes(savedLanguage)) { + console.log('🌐 使用儲存的語言設定:', savedLanguage); + return savedLanguage; + } + + // 2. 檢查瀏覽器語言 + const browserLang = navigator.language || navigator.userLanguage; + console.log('🌐 瀏覽器語言:', browserLang); + + if (browserLang.startsWith('zh-TW') || browserLang.includes('Hant')) { + console.log('🌐 偵測到繁體中文環境'); + return 'zh-TW'; + } + if (browserLang.startsWith('zh') || browserLang.includes('Hans')) { + console.log('🌐 偵測到簡體中文環境'); + return 'zh-CN'; + } + if (browserLang.startsWith('en')) { + console.log('🌐 偵測到英文環境'); + return 'en'; + } + + // 3. 預設使用繁體中文 + console.log('🌐 使用預設語言: zh-TW'); + return 'zh-TW'; + } async init() { console.log(`i18nManager 使用預設語言: ${this.currentLanguage}`); @@ -163,6 +193,16 @@ class I18nManager { } }); + // 翻譯有 data-i18n-aria-label 屬性的元素 + const ariaLabelElements = document.querySelectorAll('[data-i18n-aria-label]'); + ariaLabelElements.forEach(element => { + const key = element.getAttribute('data-i18n-aria-label'); + const translation = this.t(key); + if (translation && translation !== key) { + element.setAttribute('aria-label', translation); + } + }); + // 更新動態內容 this.updateDynamicContent(); @@ -290,31 +330,18 @@ class I18nManager { // 設定頁籤的下拉選擇器 const selector = document.getElementById('settingsLanguageSelect'); if (selector) { - // 設置當前值 + // 只設置當前值,不綁定事件(讓 SettingsManager 統一處理) selector.value = this.currentLanguage; console.log(`🔧 setupLanguageSelectors: 設置 select.value = ${this.currentLanguage}`); - - // 移除舊的事件監聽器(如果存在) - if (selector._i18nChangeHandler) { - selector.removeEventListener('change', selector._i18nChangeHandler); - } - - // 添加新的事件監聽器 - 通過 SettingsManager 統一管理 - selector._i18nChangeHandler = (e) => { - console.log(`🔄 i18n select change event: ${e.target.value}`); - if (window.feedbackApp && window.feedbackApp.settingsManager) { - window.feedbackApp.settingsManager.set('language', e.target.value); - } else { - this.setLanguage(e.target.value); - } - }; - selector.addEventListener('change', selector._i18nChangeHandler); + + // 不再綁定事件監聽器,避免與 SettingsManager 衝突 + // 事件處理完全交由 SettingsManager 負責 } // 新版現代化語言選擇器 const languageOptions = document.querySelectorAll('.language-option'); if (languageOptions.length > 0) { - // 設置當前語言的活躍狀態和點擊事件 + // 只設置當前語言的活躍狀態,不綁定事件 languageOptions.forEach(option => { const lang = option.getAttribute('data-lang'); if (lang === this.currentLanguage) { @@ -322,20 +349,8 @@ class I18nManager { } else { option.classList.remove('active'); } - - // 移除舊的事件監聽器(如果存在) - option.removeEventListener('click', option._languageClickHandler); - - // 添加新的點擊事件監聽器 - 通過 SettingsManager 統一管理 - option._languageClickHandler = () => { - if (window.feedbackApp && window.feedbackApp.settingsManager) { - window.feedbackApp.settingsManager.set('language', lang); - } else { - this.setLanguage(lang); - } - }; - option.addEventListener('click', option._languageClickHandler); }); + // 事件監聽器由 SettingsManager 統一處理,避免重複綁定 } } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js index dd6f988..2112f92 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js @@ -591,7 +591,7 @@ // 可以在這裡添加 UI 通知邏輯 if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { const message = window.i18nManager ? - window.i18nManager.t('audio.autoplayBlocked', '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知') : + window.i18nManager.t('notification.autoplayBlocked', '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知') : '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知'; window.MCPFeedback.Utils.showMessage(message, 'info'); } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js index 587a95d..cb10e17 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js @@ -85,13 +85,11 @@