mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-26 10:02:25 +08:00
🎨 重構多語系機制,讓通知也支持多語系
This commit is contained in:
parent
0917214272
commit
60e64c90dc
202
scripts/validate_message_codes.py
Normal file
202
scripts/validate_message_codes.py
Normal file
@ -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)
|
8
src/mcp_feedback_enhanced/web/constants/__init__.py
Normal file
8
src/mcp_feedback_enhanced/web/constants/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""
|
||||
Web 常量模組
|
||||
"""
|
||||
|
||||
from .message_codes import MessageCodes, get_message_code
|
||||
|
||||
|
||||
__all__ = ["MessageCodes", "get_message_code"]
|
173
src/mcp_feedback_enhanced/web/constants/message_codes.py
Normal file
173
src/mcp_feedback_enhanced/web/constants/message_codes.py
Normal file
@ -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}"
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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": "切换音效通知"
|
||||
}
|
||||
}
|
||||
|
@ -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": "切換音效通知"
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) # 給前端一點時間處理消息
|
||||
|
@ -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"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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 統一處理,避免重複綁定
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -85,13 +85,11 @@
|
||||
<!-- 啟用開關 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="audio.notification.enabled">啟用音效通知</div>
|
||||
<div class="setting-description" data-i18n="audio.notification.enabledDesc">
|
||||
啟用後將在有新會話更新時播放音效通知
|
||||
</div>
|
||||
<div class="setting-label" data-i18n="audio.notification.enabled"></div>
|
||||
<div class="setting-description" data-i18n="audio.notification.enabledDesc"></div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="audioNotificationEnabled" class="toggle-btn" aria-label="切換音效通知">
|
||||
<button type="button" id="audioNotificationEnabled" class="toggle-btn" data-i18n-aria-label="aria.toggleAudioNotification">
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</div>
|
||||
@ -668,6 +666,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 對有 data-i18n-aria-label 屬性的元素應用翻譯
|
||||
const ariaLabelElements = this.container.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);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('🌐 AudioSettingsUI 初始翻譯已應用');
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,168 @@
|
||||
/**
|
||||
* MCP Feedback Enhanced - 訊息代碼常量
|
||||
* ====================================
|
||||
*
|
||||
* 定義所有系統訊息的標準代碼,用於國際化支援
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 確保命名空間存在
|
||||
window.MCPFeedback = window.MCPFeedback || {};
|
||||
window.MCPFeedback.Constants = window.MCPFeedback.Constants || {};
|
||||
|
||||
/**
|
||||
* 訊息代碼枚舉
|
||||
* 所有系統訊息都應該使用這些代碼,而非硬編碼字串
|
||||
*/
|
||||
const MessageCodes = {
|
||||
// 系統狀態訊息
|
||||
SYSTEM: {
|
||||
CONNECTION_ESTABLISHED: 'system.connectionEstablished',
|
||||
CONNECTION_LOST: 'system.connectionLost',
|
||||
CONNECTION_RECONNECTING: 'system.connectionReconnecting',
|
||||
CONNECTION_RECONNECTED: 'system.connectionReconnected',
|
||||
CONNECTION_FAILED: 'system.connectionFailed',
|
||||
WEBSOCKET_ERROR: 'system.websocketError'
|
||||
},
|
||||
|
||||
// 會話相關訊息
|
||||
SESSION: {
|
||||
NO_ACTIVE_SESSION: 'session.noActiveSession',
|
||||
SESSION_CREATED: 'session.created',
|
||||
SESSION_UPDATED: 'session.updated',
|
||||
SESSION_EXPIRED: 'session.expired',
|
||||
SESSION_TIMEOUT: 'session.timeout',
|
||||
SESSION_CLEANED: 'session.cleaned',
|
||||
FEEDBACK_SUBMITTED: 'session.feedbackSubmitted',
|
||||
USER_MESSAGE_RECORDED: 'session.userMessageRecorded',
|
||||
HISTORY_SAVED: 'session.historySaved',
|
||||
HISTORY_LOADED: 'session.historyLoaded',
|
||||
MANUAL_CLEANUP: 'session.manualCleanup',
|
||||
ERROR_CLEANUP: 'session.errorCleanup'
|
||||
},
|
||||
|
||||
// 設定相關訊息
|
||||
SETTINGS: {
|
||||
SAVED: 'settings.saved',
|
||||
LOADED: 'settings.loaded',
|
||||
CLEARED: 'settings.cleared',
|
||||
SAVE_FAILED: 'settings.saveFailed',
|
||||
LOAD_FAILED: 'settings.loadFailed',
|
||||
CLEAR_FAILED: 'settings.clearFailed',
|
||||
INVALID_VALUE: 'settings.invalidValue',
|
||||
LOG_LEVEL_UPDATED: 'settings.logLevelUpdated',
|
||||
INVALID_LOG_LEVEL: 'settings.invalidLogLevel'
|
||||
},
|
||||
|
||||
// 通知相關訊息
|
||||
NOTIFICATION: {
|
||||
AUTOPLAY_BLOCKED: 'notification.autoplayBlocked',
|
||||
PERMISSION_DENIED: 'notification.permissionDenied',
|
||||
PERMISSION_GRANTED: 'notification.permissionGranted',
|
||||
TEST_SENT: 'notification.testSent',
|
||||
SOUND_ENABLED: 'notification.soundEnabled',
|
||||
SOUND_DISABLED: 'notification.soundDisabled'
|
||||
},
|
||||
|
||||
// 檔案上傳訊息
|
||||
FILE: {
|
||||
UPLOAD_SUCCESS: 'file.uploadSuccess',
|
||||
UPLOAD_FAILED: 'file.uploadFailed',
|
||||
SIZE_TOO_LARGE: 'file.sizeTooLarge',
|
||||
TYPE_NOT_SUPPORTED: 'file.typeNotSupported',
|
||||
PROCESSING: 'file.processing',
|
||||
REMOVED: 'file.removed'
|
||||
},
|
||||
|
||||
// 提示詞相關訊息
|
||||
PROMPT: {
|
||||
SAVED: 'prompt.saved',
|
||||
DELETED: 'prompt.deleted',
|
||||
APPLIED: 'prompt.applied',
|
||||
IMPORT_SUCCESS: 'prompt.importSuccess',
|
||||
IMPORT_FAILED: 'prompt.importFailed',
|
||||
EXPORT_SUCCESS: 'prompt.exportSuccess',
|
||||
VALIDATION_FAILED: 'prompt.validationFailed'
|
||||
},
|
||||
|
||||
// 錯誤訊息
|
||||
ERROR: {
|
||||
GENERIC: 'error.generic',
|
||||
NETWORK: 'error.network',
|
||||
SERVER: 'error.server',
|
||||
TIMEOUT: 'error.timeout',
|
||||
INVALID_INPUT: 'error.invalidInput',
|
||||
OPERATION_FAILED: 'error.operationFailed'
|
||||
},
|
||||
|
||||
// 命令執行訊息
|
||||
COMMAND: {
|
||||
EXECUTING: 'commandStatus.executing',
|
||||
COMPLETED: 'commandStatus.completed',
|
||||
FAILED: 'commandStatus.failed',
|
||||
OUTPUT_RECEIVED: 'commandStatus.outputReceived',
|
||||
INVALID_COMMAND: 'commandStatus.invalid',
|
||||
ERROR: 'commandStatus.error'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 訊息嚴重程度
|
||||
*/
|
||||
const MessageSeverity = {
|
||||
INFO: 'info',
|
||||
SUCCESS: 'success',
|
||||
WARNING: 'warning',
|
||||
ERROR: 'error'
|
||||
};
|
||||
|
||||
/**
|
||||
* 建立標準訊息物件
|
||||
* @param {string} code - 訊息代碼
|
||||
* @param {Object} params - 動態參數
|
||||
* @param {string} severity - 嚴重程度
|
||||
* @returns {Object} 標準訊息物件
|
||||
*/
|
||||
function createMessage(code, params = {}, severity = MessageSeverity.INFO) {
|
||||
return {
|
||||
type: 'notification',
|
||||
code: code,
|
||||
params: params,
|
||||
severity: severity,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷方法:建立成功訊息
|
||||
*/
|
||||
function createSuccessMessage(code, params = {}) {
|
||||
return createMessage(code, params, MessageSeverity.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷方法:建立錯誤訊息
|
||||
*/
|
||||
function createErrorMessage(code, params = {}) {
|
||||
return createMessage(code, params, MessageSeverity.ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷方法:建立警告訊息
|
||||
*/
|
||||
function createWarningMessage(code, params = {}) {
|
||||
return createMessage(code, params, MessageSeverity.WARNING);
|
||||
}
|
||||
|
||||
// 匯出到全域命名空間
|
||||
window.MCPFeedback.Constants.MessageCodes = MessageCodes;
|
||||
window.MCPFeedback.Constants.MessageSeverity = MessageSeverity;
|
||||
window.MCPFeedback.Constants.createMessage = createMessage;
|
||||
window.MCPFeedback.Constants.createSuccessMessage = createSuccessMessage;
|
||||
window.MCPFeedback.Constants.createErrorMessage = createErrorMessage;
|
||||
window.MCPFeedback.Constants.createWarningMessage = createWarningMessage;
|
||||
|
||||
console.log('📋 訊息代碼常量載入完成');
|
||||
})();
|
@ -289,14 +289,23 @@
|
||||
if (this.maxFileSize > 0 && file.size > this.maxFileSize) {
|
||||
const sizeLimit = this.formatFileSize(this.maxFileSize);
|
||||
console.warn('⚠️ 檔案過大:', file.name, '超過限制', sizeLimit);
|
||||
this.showMessage('圖片大小超過限制 (' + sizeLimit + '): ' + file.name, 'warning');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('fileUpload.fileSizeExceeded', {
|
||||
limit: sizeLimit,
|
||||
filename: file.name
|
||||
}) :
|
||||
'圖片大小超過限制 (' + sizeLimit + '): ' + file.name;
|
||||
this.showMessage(message, 'warning');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 檢查檔案數量限制
|
||||
if (this.files.length + validFiles.length >= this.maxFiles) {
|
||||
console.warn('⚠️ 檔案數量超過限制:', this.maxFiles);
|
||||
this.showMessage('最多只能上傳 ' + this.maxFiles + ' 個檔案', 'warning');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('fileUpload.maxFilesExceeded', { maxFiles: this.maxFiles }) :
|
||||
'最多只能上傳 ' + this.maxFiles + ' 個檔案';
|
||||
this.showMessage(message, 'warning');
|
||||
break;
|
||||
}
|
||||
|
||||
@ -340,7 +349,10 @@
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('❌ 檔案處理失敗:', error);
|
||||
self.showMessage('檔案處理失敗,請重試', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('fileUpload.processingFailed', '檔案處理失敗,請重試') :
|
||||
'檔案處理失敗,請重試';
|
||||
self.showMessage(message, 'error');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -322,7 +322,8 @@
|
||||
|
||||
// 從 API 載入日誌等級
|
||||
function loadLogLevelFromAPI() {
|
||||
fetch('/api/log-level')
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/log-level?lang=' + lang)
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
@ -343,7 +344,8 @@
|
||||
|
||||
// 保存日誌等級到 API
|
||||
function saveLogLevelToAPI(logLevel) {
|
||||
fetch('/api/log-level', {
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/log-level?lang=' + lang, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -360,6 +362,11 @@
|
||||
})
|
||||
.then(function(data) {
|
||||
console.log('📋 日誌等級已保存:', data.logLevel);
|
||||
// 處理訊息代碼
|
||||
if (data.messageCode && window.i18nManager) {
|
||||
const message = window.i18nManager.t(data.messageCode, data.params);
|
||||
console.log('伺服器回應:', message);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.warn('⚠️ 保存日誌等級失敗:', error);
|
||||
|
@ -55,6 +55,11 @@
|
||||
this.setupEventListeners();
|
||||
this.updateUI();
|
||||
|
||||
// 應用翻譯到動態生成的內容
|
||||
if (window.i18nManager) {
|
||||
window.i18nManager.applyTranslations();
|
||||
}
|
||||
|
||||
console.log('✅ NotificationSettings 初始化完成');
|
||||
};
|
||||
|
||||
@ -66,17 +71,15 @@
|
||||
<!-- 啟用開關 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.settingLabel">瀏覽器通知</div>
|
||||
<div class="setting-description" data-i18n="notification.description">
|
||||
新會話建立時通知(僅在背景執行時)
|
||||
</div>
|
||||
<div class="setting-label" data-i18n="notification.settingLabel"></div>
|
||||
<div class="setting-description" data-i18n="notification.description"></div>
|
||||
<!-- 權限狀態 -->
|
||||
<div id="permissionStatus" class="permission-status">
|
||||
<!-- 動態更新 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="notificationToggle" class="toggle-btn" aria-label="切換通知">
|
||||
<button type="button" id="notificationToggle" class="toggle-btn" data-i18n-aria-label="aria.toggleNotification">
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</div>
|
||||
@ -85,25 +88,25 @@
|
||||
<!-- 通知觸發情境 -->
|
||||
<div class="setting-item notification-trigger" style="display: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.triggerTitle">通知觸發情境</div>
|
||||
<div class="setting-description" data-i18n="notification.triggerDescription">選擇何時接收通知</div>
|
||||
<div class="setting-label" data-i18n="notification.triggerTitle"></div>
|
||||
<div class="setting-description" data-i18n="notification.triggerDescription"></div>
|
||||
</div>
|
||||
<div class="trigger-options">
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="focusLost" checked>
|
||||
<span data-i18n="notification.trigger.focusLost">視窗失去焦點時(切換到其他應用程式)</span>
|
||||
<span data-i18n="notification.trigger.focusLost"></span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="tabSwitch">
|
||||
<span data-i18n="notification.trigger.tabSwitch">切換到其他標籤頁時</span>
|
||||
<span data-i18n="notification.trigger.tabSwitch"></span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="background">
|
||||
<span data-i18n="notification.trigger.background">視窗最小化或隱藏時</span>
|
||||
<span data-i18n="notification.trigger.background"></span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="always">
|
||||
<span data-i18n="notification.trigger.always">總是通知(包括前景)</span>
|
||||
<span data-i18n="notification.trigger.always"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -111,12 +114,12 @@
|
||||
<!-- 測試按鈕 -->
|
||||
<div class="setting-item notification-actions" style="display: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.testTitle">測試通知</div>
|
||||
<div class="setting-description" data-i18n="notification.testDescription">發送測試通知以確認功能正常</div>
|
||||
<div class="setting-label" data-i18n="notification.testTitle"></div>
|
||||
<div class="setting-description" data-i18n="notification.testDescription"></div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="testNotification" class="btn-primary">
|
||||
<span data-i18n="notification.test">發送測試通知</span>
|
||||
<span data-i18n="notification.test"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -261,7 +264,7 @@
|
||||
const settings = this.notificationManager.getSettings();
|
||||
|
||||
if (!settings.browserSupported) {
|
||||
this.statusDiv.innerHTML = `<span data-i18n="notification.notSupported">⚠️ 您的瀏覽器不支援通知功能</span>`;
|
||||
this.statusDiv.innerHTML = `<span data-i18n="notification.notSupported"></span>`;
|
||||
this.statusDiv.className = 'permission-status status-unsupported';
|
||||
this.toggle.disabled = true;
|
||||
return;
|
||||
|
@ -370,7 +370,10 @@
|
||||
const currentSession = this.dataManager.getCurrentSession();
|
||||
|
||||
if (!currentSession) {
|
||||
this.showMessage('目前沒有活躍的會話數據', 'warning');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.noActiveSession', '目前沒有活躍的會話數據') :
|
||||
'目前沒有活躍的會話數據';
|
||||
this.showMessage(message, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -390,7 +393,10 @@
|
||||
if (sessionData) {
|
||||
this.detailsModal.showSessionDetails(sessionData);
|
||||
} else {
|
||||
this.showMessage('找不到會話資料', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.sessionNotFound', '找不到會話資料') :
|
||||
'找不到會話資料';
|
||||
this.showMessage(message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@ -595,7 +601,10 @@
|
||||
} catch (error) {
|
||||
console.error('📋 匯出會話歷史失敗:', error);
|
||||
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
|
||||
window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.management.exportFailed', { error: error.message }) :
|
||||
'匯出失敗: ' + error.message;
|
||||
window.MCPFeedback.Utils.showMessage(message, 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -623,7 +632,10 @@
|
||||
} catch (error) {
|
||||
console.error('📋 匯出單一會話失敗:', error);
|
||||
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
|
||||
window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.management.exportFailed', { error: error.message }) :
|
||||
'匯出失敗: ' + error.message;
|
||||
window.MCPFeedback.Utils.showMessage(message, 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -659,7 +671,10 @@
|
||||
} catch (error) {
|
||||
console.error('📋 清空會話歷史失敗:', error);
|
||||
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
|
||||
window.MCPFeedback.Utils.showMessage('清空失敗: ' + error.message, 'error');
|
||||
const errorMessage = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.management.clearFailed', { error: error.message }) :
|
||||
'清空失敗: ' + error.message;
|
||||
window.MCPFeedback.Utils.showMessage(errorMessage, 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -690,11 +705,17 @@
|
||||
'用戶訊息記錄已清空';
|
||||
this.showMessage(successMessage, 'success');
|
||||
} else {
|
||||
this.showMessage('清空失敗', 'error');
|
||||
const errorMessage = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.management.clearFailedGeneric', '清空失敗') :
|
||||
'清空失敗';
|
||||
this.showMessage(errorMessage, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('📋 清空用戶訊息記錄失敗:', error);
|
||||
this.showMessage('清空失敗: ' + error.message, 'error');
|
||||
const errorMessage = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.management.clearFailed', { error: error.message }) :
|
||||
'清空失敗: ' + error.message;
|
||||
this.showMessage(errorMessage, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@ -752,15 +773,24 @@
|
||||
try {
|
||||
const currentSession = this.dataManager.getCurrentSession();
|
||||
if (!currentSession) {
|
||||
this.showMessage('没有当前会话数据', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.noData', '沒有當前會話數據') :
|
||||
'沒有當前會話數據';
|
||||
this.showMessage(message, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.formatCurrentSessionContent(currentSession);
|
||||
this.copyToClipboard(content, '当前会话内容已复制到剪贴板');
|
||||
const successMessage = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.copySuccess', '當前會話內容已複製到剪貼板') :
|
||||
'當前會話內容已複製到剪貼板';
|
||||
this.copyToClipboard(content, successMessage);
|
||||
} catch (error) {
|
||||
console.error('复制当前会话内容失败:', error);
|
||||
this.showMessage('复制失败,请重试', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.copyFailed', '複製失敗,請重試') :
|
||||
'複製失敗,請重試';
|
||||
this.showMessage(message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@ -774,7 +804,10 @@
|
||||
try {
|
||||
if (!this.dataManager) {
|
||||
console.log('📝 dataManager 不存在,尝试其他方式获取数据');
|
||||
this.showMessage('数据管理器未初始化', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.dataManagerNotInit', '數據管理器未初始化') :
|
||||
'數據管理器未初始化';
|
||||
this.showMessage(message, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -783,7 +816,10 @@
|
||||
|
||||
if (!currentSession) {
|
||||
console.log('📝 没有当前会话数据');
|
||||
this.showMessage('当前会话没有数据', 'warning');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.noData', '當前會話沒有數據') :
|
||||
'當前會話沒有數據';
|
||||
this.showMessage(message, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -792,7 +828,10 @@
|
||||
|
||||
if (!currentSession.user_messages || currentSession.user_messages.length === 0) {
|
||||
console.log('📝 没有用户消息记录');
|
||||
this.showMessage('当前会话没有用户消息记录', 'warning');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.noUserMessages', '當前會話沒有用戶消息記錄') :
|
||||
'當前會話沒有用戶消息記錄';
|
||||
this.showMessage(message, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -804,11 +843,17 @@
|
||||
console.log('📝 格式化后的内容长度:', content.length);
|
||||
console.log('📝 格式化后的内容预览:', content.substring(0, 200));
|
||||
|
||||
this.copyToClipboard(content, '当前用户内容已复制到剪贴板');
|
||||
const successMessage = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.userContentCopySuccess', '當前用戶內容已複製到剪貼板') :
|
||||
'當前用戶內容已複製到剪貼板';
|
||||
this.copyToClipboard(content, successMessage);
|
||||
} catch (error) {
|
||||
console.error('📝 复制当前用户内容失败:', error);
|
||||
console.error('📝 错误堆栈:', error.stack);
|
||||
this.showMessage('复制失败,请重试', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.copyFailed', '複製失敗,請重試') :
|
||||
'複製失敗,請重試';
|
||||
this.showMessage(message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@ -959,7 +1004,10 @@
|
||||
this.showMessage(successMessage, 'success');
|
||||
} catch (err) {
|
||||
console.error('降级复制失败:', err);
|
||||
this.showMessage('复制失败,请手动复制', 'error');
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('sessionHistory.currentSession.copyFailedManual', '複製失敗,請手動複製') :
|
||||
'複製失敗,請手動複製';
|
||||
this.showMessage(message, 'error');
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
@ -327,7 +327,8 @@
|
||||
* 發送用戶消息到服務器端
|
||||
*/
|
||||
SessionDataManager.prototype.sendUserMessageToServer = function(userMessage) {
|
||||
fetch('/api/add-user-message', {
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/add-user-message?lang=' + lang, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -633,7 +634,8 @@
|
||||
const self = this;
|
||||
|
||||
// 首先嘗試獲取實時會話狀態
|
||||
fetch('/api/all-sessions')
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/all-sessions?lang=' + lang)
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
@ -677,7 +679,8 @@
|
||||
SessionDataManager.prototype.loadFromHistoryFile = function() {
|
||||
const self = this;
|
||||
|
||||
fetch('/api/load-session-history')
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/load-session-history?lang=' + lang)
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
@ -775,7 +778,8 @@
|
||||
lastCleanup: TimeUtils.getCurrentTimestamp()
|
||||
};
|
||||
|
||||
fetch('/api/save-session-history', {
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/save-session-history?lang=' + lang, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -791,7 +795,12 @@
|
||||
}
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log('📊 會話快照保存回應:', result.message);
|
||||
if (result.messageCode && window.i18nManager) {
|
||||
const message = window.i18nManager.t(result.messageCode, result.params);
|
||||
console.log('📊 會話快照保存回應:', message);
|
||||
} else {
|
||||
console.log('📊 會話快照保存回應:', result.message);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('📊 保存會話快照到伺服器失敗:', error);
|
||||
@ -807,7 +816,8 @@
|
||||
lastCleanup: TimeUtils.getCurrentTimestamp()
|
||||
};
|
||||
|
||||
fetch('/api/save-session-history', {
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/save-session-history?lang=' + lang, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -823,7 +833,12 @@
|
||||
}
|
||||
})
|
||||
.then(function(result) {
|
||||
console.log('📊 伺服器保存回應:', result.message);
|
||||
if (result.messageCode && window.i18nManager) {
|
||||
const message = window.i18nManager.t(result.messageCode, result.params);
|
||||
console.log('📊 伺服器保存回應:', message);
|
||||
} else {
|
||||
console.log('📊 伺服器保存回應:', result.message);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('📊 保存會話歷史到伺服器失敗:', error);
|
||||
|
@ -23,11 +23,14 @@
|
||||
function SettingsManager(options) {
|
||||
options = options || {};
|
||||
|
||||
// 從 i18nManager 獲取當前語言作為預設值
|
||||
const defaultLanguage = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
|
||||
// 預設設定
|
||||
this.defaultSettings = {
|
||||
layoutMode: 'combined-vertical',
|
||||
autoClose: false,
|
||||
language: 'zh-TW',
|
||||
language: defaultLanguage, // 使用 i18nManager 的當前語言
|
||||
imageSizeLimit: 0,
|
||||
enableBase64Detail: false,
|
||||
// 移除 activeTab - 頁籤切換無需持久化
|
||||
@ -80,6 +83,17 @@
|
||||
console.log('沒有找到設定,使用預設值');
|
||||
self.currentSettings = Utils.deepClone(self.defaultSettings);
|
||||
}
|
||||
|
||||
// 同步語言設定到 i18nManager
|
||||
if (self.currentSettings.language && window.i18nManager) {
|
||||
const currentI18nLanguage = window.i18nManager.getCurrentLanguage();
|
||||
if (self.currentSettings.language !== currentI18nLanguage) {
|
||||
console.log('🔧 SettingsManager.loadSettings: 同步語言設定到 i18nManager');
|
||||
console.log(' 從:', currentI18nLanguage, '到:', self.currentSettings.language);
|
||||
window.i18nManager.setLanguage(self.currentSettings.language);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(self.currentSettings);
|
||||
})
|
||||
.catch(function(error) {
|
||||
@ -94,7 +108,8 @@
|
||||
* 從伺服器載入設定
|
||||
*/
|
||||
SettingsManager.prototype.loadFromServer = function() {
|
||||
return fetch('/api/load-settings')
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
return fetch('/api/load-settings?lang=' + lang)
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
@ -146,7 +161,8 @@
|
||||
SettingsManager.prototype._performServerSave = function() {
|
||||
const self = this;
|
||||
|
||||
fetch('/api/save-settings', {
|
||||
const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
fetch('/api/save-settings?lang=' + lang, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -154,10 +170,18 @@
|
||||
body: JSON.stringify(self.currentSettings)
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
console.log('設定已即時同步到伺服器端');
|
||||
// 處理訊息代碼
|
||||
if (data.messageCode && window.i18nManager) {
|
||||
const message = window.i18nManager.t(data.messageCode, data.params);
|
||||
console.log('伺服器回應:', message);
|
||||
}
|
||||
} else {
|
||||
console.warn('同步設定到伺服器端失敗:', response.status);
|
||||
console.warn('同步設定到伺服器端失敗:', data);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
@ -239,15 +263,12 @@
|
||||
* 處理語言變更
|
||||
*/
|
||||
SettingsManager.prototype.handleLanguageChange = function(newLanguage) {
|
||||
console.log('語言設定變更: ' + newLanguage);
|
||||
console.log('🔄 SettingsManager.handleLanguageChange: ' + newLanguage);
|
||||
|
||||
// 通知國際化系統(統一由 SettingsManager 管理)
|
||||
if (window.i18nManager) {
|
||||
// 直接設定語言,不觸發 i18nManager 的保存邏輯
|
||||
window.i18nManager.currentLanguage = newLanguage;
|
||||
window.i18nManager.applyTranslations();
|
||||
window.i18nManager.setupLanguageSelectors();
|
||||
document.documentElement.lang = newLanguage;
|
||||
// 使用 setLanguage 方法確保正確更新
|
||||
window.i18nManager.setLanguage(newLanguage);
|
||||
}
|
||||
|
||||
// 延遲更新動態文字,確保 i18n 已經載入新語言
|
||||
@ -688,7 +709,10 @@
|
||||
try {
|
||||
// 如果要啟用自動提交,檢查是否已選擇提示詞
|
||||
if (newValue && (!currentPromptId || currentPromptId === '')) {
|
||||
Utils.showMessage('請先選擇一個提示詞作為自動提交內容', Utils.CONSTANTS.MESSAGE_WARNING);
|
||||
const message = window.i18nManager ?
|
||||
window.i18nManager.t('settingsUI.autoCommitNoPrompt', '請先選擇一個提示詞作為自動提交內容') :
|
||||
'請先選擇一個提示詞作為自動提交內容';
|
||||
Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -494,7 +494,9 @@
|
||||
feedbackInput.value = '';
|
||||
console.log('📝 已清空文字內容');
|
||||
}
|
||||
feedbackInput.disabled = false;
|
||||
// 只有在等待狀態才啟用輸入框
|
||||
const canInput = this.feedbackState === Utils.CONSTANTS.FEEDBACK_WAITING;
|
||||
feedbackInput.disabled = !canInput;
|
||||
}
|
||||
|
||||
// 重新啟用提交按鈕
|
||||
|
@ -169,8 +169,12 @@
|
||||
* @returns {Promise<boolean>} 複製是否成功
|
||||
*/
|
||||
copyToClipboard: function(text, successMessage, errorMessage) {
|
||||
successMessage = successMessage || '已複製到剪貼板';
|
||||
errorMessage = errorMessage || '複製失敗';
|
||||
successMessage = successMessage || (window.i18nManager ?
|
||||
window.i18nManager.t('utils.copySuccess', '已複製到剪貼板') :
|
||||
'已複製到剪貼板');
|
||||
errorMessage = errorMessage || (window.i18nManager ?
|
||||
window.i18nManager.t('utils.copyError', '複製失敗') :
|
||||
'複製失敗');
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
@ -262,7 +266,115 @@
|
||||
* @param {string} type - 訊息類型 (success, error, warning, info)
|
||||
* @param {number} duration - 顯示時間(毫秒)
|
||||
*/
|
||||
showMessage: function(message, type, duration) {
|
||||
showMessage: function(messageOrCode, type, duration) {
|
||||
// 處理訊息代碼物件
|
||||
let actualMessage = messageOrCode;
|
||||
let actualType = type || 'info';
|
||||
|
||||
if (typeof messageOrCode === 'object' && messageOrCode.code) {
|
||||
// 使用 i18n 系統翻譯訊息代碼
|
||||
if (window.i18nManager) {
|
||||
actualMessage = window.i18nManager.t(messageOrCode.code, messageOrCode.params);
|
||||
} else {
|
||||
// 改善 fallback 機制:提供基本的英文訊息
|
||||
actualMessage = this.getFallbackMessage(messageOrCode.code, messageOrCode.params);
|
||||
}
|
||||
// 使用訊息物件中的嚴重程度
|
||||
actualType = messageOrCode.severity || type || 'info';
|
||||
}
|
||||
|
||||
// 呼叫內部顯示方法
|
||||
return this._displayMessage(actualMessage, actualType, duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* 獲取 fallback 訊息
|
||||
* 當 i18n 系統尚未載入時使用
|
||||
* @param {string} code - 訊息代碼
|
||||
* @param {Object} params - 參數
|
||||
* @returns {string} fallback 訊息
|
||||
*/
|
||||
getFallbackMessage: function(code, params) {
|
||||
// 基本的 fallback 訊息對照表
|
||||
const fallbackMessages = {
|
||||
// 系統相關
|
||||
'system.connectionEstablished': 'WebSocket connection established',
|
||||
'system.connectionLost': 'WebSocket connection lost',
|
||||
'system.connectionReconnecting': 'Reconnecting...',
|
||||
'system.connectionReconnected': 'Reconnected',
|
||||
'system.connectionFailed': 'Connection failed',
|
||||
'system.websocketError': 'WebSocket error',
|
||||
'system.websocketReady': 'WebSocket ready',
|
||||
'system.memoryPressure': 'Memory pressure cleanup',
|
||||
'system.shutdown': 'System shutdown',
|
||||
'system.processKilled': 'Process killed',
|
||||
'system.heartbeatStopped': 'Heartbeat stopped',
|
||||
|
||||
// 會話相關
|
||||
'session.noActiveSession': 'No active session',
|
||||
'session.created': 'New session created',
|
||||
'session.updated': 'Session updated',
|
||||
'session.expired': 'Session expired',
|
||||
'session.timeout': 'Session timed out',
|
||||
'session.cleaned': 'Session cleaned',
|
||||
'session.feedbackSubmitted': 'Feedback submitted successfully',
|
||||
'session.userMessageRecorded': 'User message recorded',
|
||||
'session.historySaved': 'Session history saved',
|
||||
'session.historyLoaded': 'Session history loaded',
|
||||
|
||||
// 設定相關
|
||||
'settings.saved': 'Settings saved',
|
||||
'settings.loaded': 'Settings loaded',
|
||||
'settings.cleared': 'Settings cleared',
|
||||
'settings.saveFailed': 'Save failed',
|
||||
'settings.loadFailed': 'Load failed',
|
||||
'settings.clearFailed': 'Clear failed',
|
||||
'settings.setFailed': 'Set failed',
|
||||
'settings.logLevelUpdated': 'Log level updated',
|
||||
'settings.invalidLogLevel': 'Invalid log level',
|
||||
|
||||
// 錯誤相關
|
||||
'error.generic': 'An error occurred',
|
||||
'error.userMessageFailed': 'Failed to add user message',
|
||||
'error.getSessionsFailed': 'Failed to get sessions',
|
||||
'error.getLogLevelFailed': 'Failed to get log level',
|
||||
'error.command': 'Command execution error',
|
||||
'error.resourceCleanup': 'Resource cleanup error',
|
||||
'error.processing': 'Processing error',
|
||||
|
||||
// 通知相關
|
||||
'notification.autoplayBlocked': 'Browser blocked autoplay, click page to enable sound',
|
||||
|
||||
// 預設訊息
|
||||
'default': 'System message'
|
||||
};
|
||||
|
||||
// 嘗試獲取對應的 fallback 訊息
|
||||
let message = fallbackMessages[code] || fallbackMessages['default'];
|
||||
|
||||
// 處理參數替換(簡單版本)
|
||||
if (params && typeof params === 'object') {
|
||||
for (const key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
const placeholder = '{{' + key + '}}';
|
||||
message = message.replace(placeholder, params[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在開發模式下顯示警告
|
||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||
console.warn('[i18n] Fallback message used for:', code, '→', message);
|
||||
}
|
||||
|
||||
return message;
|
||||
},
|
||||
|
||||
/**
|
||||
* 內部方法:實際顯示訊息
|
||||
* @private
|
||||
*/
|
||||
_displayMessage: function(message, type, duration) {
|
||||
type = type || 'info';
|
||||
duration = duration || 3000;
|
||||
|
||||
|
@ -74,7 +74,10 @@
|
||||
this.websocket = null;
|
||||
}
|
||||
|
||||
this.websocket = new WebSocket(wsUrl);
|
||||
// 添加語言參數到 WebSocket URL
|
||||
const language = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW';
|
||||
const wsUrlWithLang = wsUrl + (wsUrl.includes('?') ? '&' : '?') + 'lang=' + language;
|
||||
this.websocket = new WebSocket(wsUrlWithLang);
|
||||
this.setupWebSocketEvents();
|
||||
|
||||
} catch (error) {
|
||||
@ -261,6 +264,11 @@
|
||||
console.log('WebSocket 連接確認');
|
||||
this.connectionReady = true;
|
||||
this.handleConnectionReady();
|
||||
// 處理訊息代碼
|
||||
if (data.messageCode && window.i18nManager) {
|
||||
const message = window.i18nManager.t(data.messageCode);
|
||||
Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_SUCCESS);
|
||||
}
|
||||
break;
|
||||
case 'heartbeat_response':
|
||||
this.handleHeartbeatResponse();
|
||||
|
@ -693,13 +693,13 @@
|
||||
<!-- 介面設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="settings.interface">🎨 介面設定</h3>
|
||||
<h3 class="settings-card-title" data-i18n="settingsUI.interface">🎨 介面設定</h3>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="settings.layoutMode">界面佈局模式</div>
|
||||
<div class="setting-description" data-i18n="settings.layoutModeDesc">
|
||||
<div class="setting-label" data-i18n="settingsUI.layoutMode">界面佈局模式</div>
|
||||
<div class="setting-description" data-i18n="settingsUI.layoutModeDesc">
|
||||
選擇 AI 摘要和回饋輸入的顯示方式
|
||||
</div>
|
||||
</div>
|
||||
@ -707,15 +707,15 @@
|
||||
<div class="layout-option">
|
||||
<input type="radio" id="combinedVertical" name="layoutMode" value="combined-vertical" checked>
|
||||
<label for="combinedVertical">
|
||||
<div class="layout-option-title" data-i18n="settings.combinedVertical">垂直布局</div>
|
||||
<div class="layout-option-desc" data-i18n="settings.combinedVerticalDesc">AI 摘要在上,回饋輸入在下,摘要和回饋在同一頁面</div>
|
||||
<div class="layout-option-title" data-i18n="settingsUI.combinedVertical">垂直布局</div>
|
||||
<div class="layout-option-desc" data-i18n="settingsUI.combinedVerticalDesc">AI 摘要在上,回饋輸入在下,摘要和回饋在同一頁面</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="layout-option">
|
||||
<input type="radio" id="combinedHorizontal" name="layoutMode" value="combined-horizontal">
|
||||
<label for="combinedHorizontal">
|
||||
<div class="layout-option-title" data-i18n="settings.combinedHorizontal">水平布局</div>
|
||||
<div class="layout-option-desc" data-i18n="settings.combinedHorizontalDesc">AI 摘要在左,回饋輸入在右,增大摘要可視區域</div>
|
||||
<div class="layout-option-title" data-i18n="settingsUI.combinedHorizontal">水平布局</div>
|
||||
<div class="layout-option-desc" data-i18n="settingsUI.combinedHorizontalDesc">AI 摘要在左,回饋輸入在右,增大摘要可視區域</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -729,13 +729,13 @@
|
||||
<!-- 語言設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="settings.language">🌍 語言設定</h3>
|
||||
<h3 class="settings-card-title" data-i18n="settingsUI.language">🌍 語言設定</h3>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="settings.currentLanguage">當前語言</div>
|
||||
<div class="setting-description" data-i18n="settings.languageDesc">
|
||||
<div class="setting-label" data-i18n="settingsUI.currentLanguage">當前語言</div>
|
||||
<div class="setting-description" data-i18n="settingsUI.languageDesc">
|
||||
選擇界面顯示語言
|
||||
</div>
|
||||
</div>
|
||||
@ -804,7 +804,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="autoSubmitToggle" class="toggle-btn" aria-label="切換自動提交">
|
||||
<button type="button" id="autoSubmitToggle" class="toggle-btn" data-i18n-aria-label="aria.toggleAutoSubmit">
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</div>
|
||||
@ -962,18 +962,18 @@
|
||||
<!-- 重置設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="settings.advanced">🔧 進階設定</h3>
|
||||
<h3 class="settings-card-title" data-i18n="settingsUI.advanced">🔧 進階設定</h3>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="setting-item" style="border-bottom: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="settings.reset">重置設定</div>
|
||||
<div class="setting-description" data-i18n="settings.resetDesc">
|
||||
<div class="setting-label" data-i18n="settingsUI.reset">重置設定</div>
|
||||
<div class="setting-description" data-i18n="settingsUI.resetDesc">
|
||||
清除所有已保存的設定,恢復到預設狀態
|
||||
</div>
|
||||
</div>
|
||||
<button id="resetSettingsBtn" class="btn btn-secondary" style="font-size: 12px; padding: 6px 16px;">
|
||||
<span data-i18n="settings.reset">重置設定</span>
|
||||
<span data-i18n="settingsUI.reset">重置設定</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user