diff --git a/src/mcp_feedback_enhanced/static/i18n.js b/src/mcp_feedback_enhanced/static/i18n.js deleted file mode 100644 index 7c37f62..0000000 --- a/src/mcp_feedback_enhanced/static/i18n.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * 前端國際化支援 - 新架構版本 - * ============================= - * - * 提供 Web UI 的多語系支援,支援繁體中文、英文、簡體中文。 - * 新特性: - * - 支援從 API 動態載入翻譯檔案 - * - 巢狀翻譯鍵值支援 - * - 舊格式兼容 - * - 自動偵測瀏覽器語言 - */ - -class I18nManager { - constructor() { - this.currentLanguage = null; - this.translations = {}; - this.supportedLanguages = ['zh-TW', 'en', 'zh-CN']; - this.fallbackLanguage = 'en'; - this.isLoaded = false; - - // 內嵌的備用翻譯(防止 API 載入失敗) - this.fallbackTranslations = this._getEmbeddedTranslations(); - - // 初始化語言設定 - this.currentLanguage = this.detectLanguage(); - } - - /** - * 獲取內嵌的備用翻譯(僅保留基本錯誤訊息) - */ - _getEmbeddedTranslations() { - return { - 'zh-TW': { - app: { title: 'Interactive Feedback MCP' }, - loading: '載入中...', - error: '載入失敗', - retry: '重試' - }, - 'en': { - app: { title: 'Interactive Feedback MCP' }, - loading: 'Loading...', - error: 'Loading failed', - retry: 'Retry' - }, - 'zh-CN': { - app: { title: 'Interactive Feedback MCP' }, - loading: '加载中...', - error: '加载失败', - retry: '重试' - } - }; - } - - /** - * 從 API 載入翻譯檔案 - */ - async loadTranslations() { - try { - // 嘗試從 API 載入翻譯 - const response = await fetch('/api/translations'); - if (response.ok) { - const data = await response.json(); - this.translations = data; - this.isLoaded = true; - console.log('[I18N] 成功從 API 載入翻譯'); - return true; - } - } catch (error) { - console.warn('[I18N] 無法從 API 載入翻譯,使用內嵌翻譯:', error); - } - - // 使用內嵌翻譯作為備用 - this.translations = this.fallbackTranslations; - this.isLoaded = true; - console.log('[I18N] 使用內嵌翻譯'); - return false; - } - - /** - * 自動偵測語言 - */ - detectLanguage() { - // 1. 先檢查 localStorage - const savedLang = localStorage.getItem('mcp-feedback-language'); - if (savedLang && this.supportedLanguages.includes(savedLang)) { - return savedLang; - } - - // 2. 檢查瀏覽器語言設定 - const browserLang = navigator.language || navigator.userLanguage; - - // 映射常見的語言代碼 - const langMap = { - 'zh-TW': 'zh-TW', - 'zh-HK': 'zh-TW', - 'zh-MO': 'zh-TW', - 'zh-CN': 'zh-CN', - 'zh-SG': 'zh-CN', - 'zh': 'zh-TW', // 默認繁體中文 - 'en': 'en', - 'en-US': 'en', - 'en-GB': 'en', - 'en-AU': 'en', - 'en-CA': 'en' - }; - - if (langMap[browserLang]) { - return langMap[browserLang]; - } - - // 3. 檢查語言前綴 - const prefix = browserLang.split('-')[0]; - if (langMap[prefix]) { - return langMap[prefix]; - } - - // 4. 回退到默認語言 - return this.fallbackLanguage; - } - - /** - * 設定語言 - */ - setLanguage(language) { - if (!this.supportedLanguages.includes(language)) { - console.warn(`Unsupported language: ${language}`); - return false; - } - - this.currentLanguage = language; - localStorage.setItem('mcp-feedback-language', language); - - // 觸發語言變更事件 - document.dispatchEvent(new CustomEvent('languageChanged', { - detail: { language: language } - })); - - return true; - } - - /** - * 從巢狀物件中獲取值 - */ - _getNestedValue(obj, path) { - return path.split('.').reduce((current, key) => { - return current && current[key] !== undefined ? current[key] : null; - }, obj); - } - - /** - * 舊鍵到新鍵的映射 - */ - _getLegacyMapping() { - return { - // 應用程式標題 - 'app_title': 'app.title', - 'project_directory': 'app.projectDirectory', - 'language_selector': 'languageSelector', - - // 語言名稱 - 'lang_zh_tw': 'languageNames.zhTw', - 'lang_en': 'languageNames.en', - 'lang_zh_cn': 'languageNames.zhCn', - - // AI 摘要區域 - 'ai_summary': 'aiSummary', - - // 分頁標籤 - 'feedback_tab': 'tabs.feedback', - 'command_tab': 'tabs.command', - - // 回饋區域 - 'feedback_title': 'feedback.title', - 'feedback_description': 'feedback.description', - 'feedback_placeholder': 'feedback.placeholder', - - // 命令區域 - 'command_title': 'command.title', - 'command_description': 'command.description', - 'command_placeholder': 'command.placeholder', - 'command_output': 'command.output', - - // 圖片區域 - 'images_title': 'images.title', - 'images_select': 'images.select', - 'images_paste': 'images.paste', - 'images_clear': 'images.clear', - 'images_status': 'images.status', - 'images_status_with_size': 'images.statusWithSize', - 'images_drag_hint': 'images.dragHint', - 'images_delete_confirm': 'images.deleteConfirm', - 'images_delete_title': 'images.deleteTitle', - 'images_size_warning': 'images.sizeWarning', - 'images_format_error': 'images.formatError', - - // 按鈕 - 'btn_select_files': 'buttons.selectFiles', - 'btn_paste_clipboard': 'buttons.pasteClipboard', - 'btn_clear_all': 'buttons.clearAll', - 'btn_run_command': 'buttons.runCommand', - 'btn_submit_feedback': 'buttons.submitFeedback', - 'btn_cancel': 'buttons.cancel', - - // 狀態消息 - 'uploading': 'status.uploading', - 'upload_success': 'status.uploadSuccess', - 'upload_failed': 'status.uploadFailed', - 'command_running': 'status.commandRunning', - 'command_finished': 'status.commandFinished', - 'paste_success': 'status.pasteSuccess', - 'paste_failed': 'status.pasteFailed', - 'paste_no_image': 'status.paste_no_image', - 'paste_image_from_textarea': 'status.paste_image_from_textarea', - 'invalid_file_type': 'status.invalidFileType', - 'file_too_large': 'status.fileTooLarge' - }; - } - - /** - * 獲取翻譯文字 - */ - t(key, params = {}) { - // 確保翻譯已載入 - if (!this.isLoaded) { - // 如果還沒載入,先嘗試從備用翻譯獲取 - this.translations = this.fallbackTranslations; - } - - const currentTranslations = this.translations[this.currentLanguage] || {}; - - // 嘗試新格式(巢狀鍵) - let translation = this._getNestedValue(currentTranslations, key); - - // 如果沒有找到,嘗試舊格式映射 - if (translation === null) { - const legacyMapping = this._getLegacyMapping(); - const newKey = legacyMapping[key]; - if (newKey) { - translation = this._getNestedValue(currentTranslations, newKey); - } - } - - // 如果還是沒有找到,嘗試回退語言 - if (translation === null) { - const fallbackTranslations = this.translations[this.fallbackLanguage] || {}; - translation = this._getNestedValue(fallbackTranslations, key); - - if (translation === null) { - const legacyMapping = this._getLegacyMapping(); - const newKey = legacyMapping[key]; - if (newKey) { - translation = this._getNestedValue(fallbackTranslations, newKey); - } - } - } - - // 最後回退到鍵本身 - if (translation === null) { - translation = key; - } - - // 替換參數 - if (typeof translation === 'string') { - translation = translation.replace(/{(\w+)}/g, (match, param) => { - return params[param] !== undefined ? params[param] : match; - }); - } - - return translation; - } - - /** - * 獲取語言顯示名稱 - */ - getLanguageDisplayName(languageCode) { - const key = `languageNames.${languageCode.toLowerCase().replace('-', '')}`; - if (languageCode === 'zh-TW') { - return this.t('languageNames.zhTw'); - } else if (languageCode === 'zh-CN') { - return this.t('languageNames.zhCn'); - } else if (languageCode === 'en') { - return this.t('languageNames.en'); - } - return this.t(key); - } - - /** - * 獲取當前語言 - */ - getCurrentLanguage() { - return this.currentLanguage; - } - - /** - * 獲取支援的語言列表 - */ - getSupportedLanguages() { - return [...this.supportedLanguages]; - } - - /** - * 初始化(載入翻譯) - */ - async init() { - await this.loadTranslations(); - return this.isLoaded; - } -} - -// 創建全域實例 -window.i18n = new I18nManager(); - -// 翻譯函數的全域快捷方式 -window.t = function(key, params = {}) { - return window.i18n.t(key, params); -}; - -// 初始化函數 -window.initI18n = async function() { - await window.i18n.init(); - return window.i18n.isLoaded; -}; \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/static/style.css b/src/mcp_feedback_enhanced/static/style.css deleted file mode 100644 index c4da73a..0000000 --- a/src/mcp_feedback_enhanced/static/style.css +++ /dev/null @@ -1,447 +0,0 @@ -/* Interactive Feedback MCP - Modern Dark Theme */ -:root { - --primary-color: #007acc; - --primary-hover: #005999; - --background-color: #1e1e1e; - --surface-color: #2d2d30; - --surface-hover: #383838; - --text-primary: #cccccc; - --text-secondary: #9e9e9e; - --text-accent: #007acc; - --border-color: #464647; - --success-color: #4caf50; - --warning-color: #ff9800; - --error-color: #f44336; - --console-bg: #1a1a1a; - --input-bg: #2d2d30; - --button-bg: #0e639c; - --button-hover-bg: #1177bb; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: var(--background-color); - color: var(--text-primary); - line-height: 1.6; - min-height: 100vh; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - min-height: 100vh; - display: flex; - flex-direction: column; -} - -h1 { - text-align: center; - color: var(--text-accent); - margin-bottom: 30px; - font-size: 2.5em; - font-weight: 300; -} - -h2, h3 { - color: var(--text-primary); - margin-bottom: 15px; -} - -h3 { - font-size: 1.3em; - font-weight: 500; -} - -.section { - background-color: var(--surface-color); - border-radius: 8px; - padding: 20px; - margin-bottom: 20px; - border: 1px solid var(--border-color); - transition: all 0.3s ease; -} - -.section:hover { - background-color: var(--surface-hover); -} - -.session-info { - background: linear-gradient(135deg, var(--surface-color), var(--surface-hover)); - border-left: 4px solid var(--primary-color); -} - -.session-info p { - margin-bottom: 8px; - font-size: 1.1em; -} - -.session-info strong { - color: var(--text-accent); -} - -.toggle-btn { - width: 100%; - background-color: var(--button-bg); - color: white; - border: none; - padding: 12px 20px; - border-radius: 6px; - font-size: 1.1em; - cursor: pointer; - transition: all 0.3s ease; - margin-bottom: 10px; -} - -.toggle-btn:hover { - background-color: var(--button-hover-bg); - transform: translateY(-1px); -} - -.command-section { - animation: slideDown 0.3s ease-out; -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.input-group { - display: flex; - gap: 10px; - margin-bottom: 15px; - align-items: center; -} - -.input-group input { - flex: 1; - background-color: var(--input-bg); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 12px 15px; - color: var(--text-primary); - font-size: 14px; - transition: all 0.3s ease; -} - -.input-group input:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); -} - -.input-group button { - background-color: var(--button-bg); - color: white; - border: none; - padding: 12px 20px; - border-radius: 6px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: all 0.3s ease; - min-width: 80px; -} - -.input-group button:hover { - background-color: var(--button-hover-bg); - transform: translateY(-1px); -} - -#stop-btn { - background-color: var(--error-color); -} - -#stop-btn:hover { - background-color: #d32f2f; -} - -.checkbox-group { - display: flex; - align-items: center; - gap: 15px; - margin-bottom: 20px; - padding: 10px 0; -} - -.checkbox-group label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 14px; -} - -.checkbox-group input[type="checkbox"] { - width: 18px; - height: 18px; - accent-color: var(--primary-color); -} - -#save-config { - background-color: var(--success-color); - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - font-size: 13px; - transition: all 0.3s ease; -} - -#save-config:hover { - background-color: #45a049; -} - -.console-section { - margin-top: 20px; -} - -.console-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; -} - -.console-header h4 { - margin: 0; - color: var(--text-secondary); -} - -#clear-logs { - background-color: var(--warning-color); - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - transition: all 0.3s ease; -} - -#clear-logs:hover { - background-color: #f57c00; -} - -.console { - background-color: var(--console-bg); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 15px; - height: 300px; - overflow-y: auto; - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - font-size: 13px; - line-height: 1.4; - white-space: pre-wrap; - word-wrap: break-word; -} - -.console-line { - margin-bottom: 2px; - color: var(--text-primary); -} - -.console::-webkit-scrollbar { - width: 8px; -} - -.console::-webkit-scrollbar-track { - background: var(--surface-color); - border-radius: 4px; -} - -.console::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 4px; -} - -.console::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); -} - -.feedback-section { - background: linear-gradient(135deg, var(--surface-color), var(--surface-hover)); - border-left: 4px solid var(--success-color); - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.feedback-description { - background-color: var(--input-bg); - padding: 15px; - border-radius: 6px; - margin-bottom: 15px; - border-left: 3px solid var(--primary-color); - font-style: italic; - color: var(--text-secondary); -} - -#feedback-input { - flex-grow: 1; - min-height: 150px; - background-color: var(--input-bg); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 15px; - color: var(--text-primary); - font-size: 14px; - font-family: inherit; - resize: vertical; - transition: all 0.3s ease; - margin-bottom: 15px; -} - -#feedback-input:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); -} - -#feedback-input::placeholder { - color: var(--text-secondary); -} - -#submit-feedback { - background: linear-gradient(135deg, var(--success-color), #66bb6a); - color: white; - border: none; - padding: 15px 30px; - border-radius: 6px; - cursor: pointer; - font-size: 16px; - font-weight: 600; - transition: all 0.3s ease; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -#submit-feedback:hover { - background: linear-gradient(135deg, #45a049, #4caf50); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); -} - -.footer { - text-align: center; - margin-top: 30px; - padding: 20px; - color: var(--text-secondary); - font-size: 13px; - border-top: 1px solid var(--border-color); -} - -.footer a { - color: var(--text-accent); - text-decoration: none; - transition: color 0.3s ease; -} - -.footer a:hover { - color: var(--primary-hover); - text-decoration: underline; -} - -.loading { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.8); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.spinner { - width: 50px; - height: 50px; - border: 4px solid var(--border-color); - border-top: 4px solid var(--primary-color); - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 20px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.loading p { - color: var(--text-primary); - font-size: 18px; - font-weight: 500; -} - -/* Responsive Design */ -@media (max-width: 768px) { - .container { - padding: 15px; - } - - h1 { - font-size: 2em; - margin-bottom: 20px; - } - - .input-group { - flex-direction: column; - align-items: stretch; - } - - .input-group button { - margin-top: 10px; - } - - .checkbox-group { - flex-direction: column; - align-items: flex-start; - gap: 10px; - } - - .console { - height: 200px; - } - - #feedback-input { - min-height: 120px; - } -} - -/* Smooth transitions for all interactive elements */ -* { - transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease; -} - -/* Focus styles for accessibility */ -button:focus, -input:focus, -textarea:focus { - outline: 2px solid var(--primary-color); - outline-offset: 2px; -} - -/* Custom selection colors */ -::selection { - background-color: var(--primary-color); - color: white; -} \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/templates/feedback.html b/src/mcp_feedback_enhanced/templates/feedback.html deleted file mode 100644 index bc8c19e..0000000 --- a/src/mcp_feedback_enhanced/templates/feedback.html +++ /dev/null @@ -1,1592 +0,0 @@ - - - -
- - -🟢 MCP 服務器正在運行
-等待來自 AI 助手的互動請求...
-這是一個 Model Context Protocol (MCP) 服務器,用於在 AI 輔助開發工具中提供人在回路的互動回饋功能。
-當 AI 助手需要用戶回饋時,會自動在瀏覽器中開啟互動頁面。
-