From d6284338c63e8389182ea2a93d4454d2fe2ff2cc Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sat, 14 Jun 2025 11:38:14 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E4=B8=8A=E6=96=B9=E7=89=88?= =?UTF-8?q?=E9=9D=A2=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/locales/en/translation.json | 6 + .../web/locales/zh-CN/translation.json | 6 + .../web/locales/zh-TW/translation.json | 6 + .../web/static/css/session-management.css | 173 ++++++++++- .../web/static/js/i18n.js | 10 + .../js/modules/session/session-ui-renderer.js | 159 +++++++++- .../web/static/js/modules/utils.js | 119 ++++++++ .../web/templates/feedback.html | 67 ++-- tests/helpers/manual_tooltip_test.html | 288 ++++++++++++++++++ 9 files changed, 801 insertions(+), 33 deletions(-) create mode 100644 tests/helpers/manual_tooltip_test.html diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index b365da2..b4dbb07 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -3,6 +3,12 @@ "title": "MCP Interactive Feedback System", "subtitle": "AI Assistant Interactive Feedback Platform", "projectDirectory": "Project Directory", + "clickToCopyPath": "Click to copy full path", + "clickToCopySessionId": "Click to copy full session ID", + "pathCopied": "Project path copied to clipboard", + "pathCopyFailed": "Failed to copy path", + "sessionIdCopied": "Session ID copied to clipboard", + "sessionIdCopyFailed": "Failed to copy session ID", "updateFailed": "Failed to update content, please manually refresh the page to view new AI work summary" }, "tabs": { diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index 9dfd2f4..12bb108 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -3,6 +3,12 @@ "title": "MCP 交互反馈系统", "subtitle": "AI 助手交互反馈平台", "projectDirectory": "项目目录", + "clickToCopyPath": "点击复制完整路径", + "clickToCopySessionId": "点击复制完整会话ID", + "pathCopied": "项目路径已复制到剪贴板", + "pathCopyFailed": "复制路径失败", + "sessionIdCopied": "会话ID已复制到剪贴板", + "sessionIdCopyFailed": "复制会话ID失败", "updateFailed": "更新内容失败,请手动刷新页面以查看新的 AI 工作摘要" }, "tabs": { diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index a31ff2a..71f43c6 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -8,6 +8,12 @@ "authorLink": "GitHub: Minidoracat", "credits": "⭐ 如果這個專案對您有幫助,請在 GitHub 上給我們一個星星!\n\n本增強版本由 Minidoracat 開發和維護,大幅擴展了專案功能,新增了 Web UI 介面、圖片支援、多語言能力以及許多其他改進功能。\n\n同時感謝 sanshao85 的 mcp-feedback-collector 專案提供的 UI 設計靈感。\n\n開源協作讓技術變得更美好!", "projectDirectory": "專案目錄", + "clickToCopyPath": "點擊複製完整路徑", + "clickToCopySessionId": "點擊複製完整會話ID", + "pathCopied": "專案路徑已複製到剪貼板", + "pathCopyFailed": "複製路徑失敗", + "sessionIdCopied": "會話ID已複製到剪貼板", + "sessionIdCopyFailed": "複製會話ID失敗", "updateFailed": "更新內容失敗,請手動刷新頁面以查看新的 AI 工作摘要" }, "tabs": { diff --git a/src/mcp_feedback_enhanced/web/static/css/session-management.css b/src/mcp_feedback_enhanced/web/static/css/session-management.css index e2e2ea2..689e2eb 100644 --- a/src/mcp_feedback_enhanced/web/static/css/session-management.css +++ b/src/mcp_feedback_enhanced/web/static/css/session-management.css @@ -107,6 +107,170 @@ align-items: center; gap: 16px; flex: 2; + justify-content: space-between; +} + +/* 會話狀態資訊區域 */ +.session-status-info { + display: flex; + flex-direction: column; + gap: 4px; + flex-shrink: 0; + min-width: 0; +} + +.session-status-info .current-session-info { + display: flex; + flex-direction: column; + gap: 2px; +} + +.session-status-info .session-indicator { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-secondary); +} + +.session-status-info .session-age { + font-size: 10px; + color: var(--text-secondary); + opacity: 0.8; + margin-left: 20px; +} + +/* 會話ID顯示樣式 */ +.session-id-display { + font-family: 'Consolas', 'Monaco', monospace; + color: var(--accent-color); + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; + background: rgba(0, 122, 204, 0.1); + border: 1px solid transparent; + transition: all var(--transition-fast) ease; + position: relative; + font-weight: 500; +} + +.session-id-display:hover { + background: rgba(0, 122, 204, 0.2); + border-color: var(--accent-color); + transform: scale(1.02); +} + +.session-id-display:active { + transform: scale(0.98); +} + +/* 會話ID tooltip */ +.session-id-display::after { + content: attr(data-full-id); + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background: var(--bg-primary); + color: var(--text-primary); + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + white-space: nowrap; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + border: 1px solid var(--border-color); + opacity: 0; + visibility: hidden; + transition: all var(--transition-fast) ease; + z-index: 1000; + margin-top: 4px; +} + +.session-id-display:hover::after { + opacity: 1; + visibility: visible; +} + +/* 專案路徑顯示樣式 - 模仿會話ID顯示 */ +.project-path-display { + font-family: 'Consolas', 'Monaco', monospace; + color: var(--accent-color); + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; + background: rgba(0, 122, 204, 0.1); + border: 1px solid transparent; + transition: all var(--transition-fast) ease; + position: relative; + font-weight: 500; + font-size: 11px; +} + +.project-path-display:hover { + background: rgba(0, 122, 204, 0.2); + border-color: var(--accent-color); + transform: scale(1.02); +} + +.project-path-display:active { + transform: scale(0.98); +} + +/* 專案路徑 tooltip */ +.project-path-display::after { + content: attr(data-full-path); + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background: var(--bg-primary); + color: var(--text-primary); + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + white-space: nowrap; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + border: 1px solid var(--border-color); + opacity: 0; + visibility: hidden; + transition: all var(--transition-fast) ease; + z-index: 1000; + margin-top: 4px; + max-width: 400px; + word-break: break-all; +} + +.project-path-display:hover::after { + opacity: 1; + visibility: visible; +} + +/* 專案路徑 tooltip 位置自動調整 */ +.project-path-display.tooltip-up::after { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: 4px; +} + +.project-path-display.tooltip-left::after { + left: 0; + transform: translateX(0); +} + +.project-path-display.tooltip-right::after { + left: auto; + right: 0; + transform: translateX(0); +} + +/* 連線和狀態資訊組合容器 */ +.connection-status-combined { + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; + flex: 1; justify-content: center; } @@ -114,7 +278,7 @@ .detailed-status-info { display: flex; gap: 16px; - margin-left: 16px; + margin-left: 0; } .websocket-metrics, @@ -680,9 +844,14 @@ flex-wrap: wrap; } + .connection-status-combined { + width: 100%; + align-items: center; + } + .detailed-status-info { margin-left: 0; - margin-top: 8px; + margin-top: 0; justify-content: center; flex-wrap: wrap; } diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index 8bb2257..a2f4c2c 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -161,6 +161,16 @@ class I18nManager { } }); + // 翻譯有 data-i18n-title 屬性的元素 + const titleElements = document.querySelectorAll('[data-i18n-title]'); + titleElements.forEach(element => { + const key = element.getAttribute('data-i18n-title'); + const translation = this.t(key); + if (translation && translation !== key) { + element.title = translation; + } + }); + // 更新動態內容 this.updateDynamicContent(); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-ui-renderer.js b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-ui-renderer.js index a6c9408..4480119 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-ui-renderer.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-ui-renderer.js @@ -36,6 +36,7 @@ this.currentSessionData = null; this.initializeElements(); + this.initializeProjectPathDisplay(); this.startActiveTimeTimer(); console.log('🎨 SessionUIRenderer 初始化完成'); @@ -55,6 +56,58 @@ }; }; + /** + * 初始化專案路徑顯示 + */ + SessionUIRenderer.prototype.initializeProjectPathDisplay = function() { + console.log('🎨 初始化專案路徑顯示'); + + const projectPathElement = document.getElementById('projectPathDisplay'); + console.log('🎨 初始化時找到專案路徑元素:', !!projectPathElement); + + if (projectPathElement) { + const fullPath = projectPathElement.getAttribute('data-full-path'); + console.log('🎨 初始化時的完整路徑:', fullPath); + + if (fullPath) { + // 使用工具函數截斷路徑 + const pathResult = window.MCPFeedback.Utils.truncatePathFromRight(fullPath, 2, 40); + console.log('🎨 初始化時路徑處理:', { fullPath, shortPath: pathResult.truncated }); + + // 更新顯示文字 + DOMUtils.safeSetTextContent(projectPathElement, pathResult.truncated); + + // 添加點擊複製功能 + if (!projectPathElement.hasAttribute('data-copy-handler')) { + console.log('🎨 初始化時添加點擊複製功能'); + projectPathElement.setAttribute('data-copy-handler', 'true'); + projectPathElement.addEventListener('click', function() { + console.log('🎨 初始化的專案路徑被點擊'); + const fullPath = this.getAttribute('data-full-path'); + console.log('🎨 初始化時準備複製路徑:', fullPath); + + if (fullPath) { + const successMessage = window.i18nManager ? + window.i18nManager.t('app.pathCopied', '專案路徑已複製到剪貼板') : + '專案路徑已複製到剪貼板'; + const errorMessage = window.i18nManager ? + window.i18nManager.t('app.pathCopyFailed', '複製路徑失敗') : + '複製路徑失敗'; + + console.log('🎨 初始化時調用複製函數'); + window.MCPFeedback.Utils.copyToClipboard(fullPath, successMessage, errorMessage); + } + }); + } else { + console.log('🎨 初始化時點擊複製功能已存在'); + } + + // 添加 tooltip 位置自動調整 + this.adjustTooltipPosition(projectPathElement); + } + } + }; + /** * 渲染當前會話 */ @@ -145,6 +198,88 @@ const projectLabel = window.i18nManager ? window.i18nManager.t('sessionManagement.project') : '專案'; DOMUtils.safeSetTextContent(projectElement, projectLabel + ': ' + projectDir); } + + // 更新頂部狀態列的專案路徑顯示 + this.updateTopProjectPathDisplay(sessionData); + }; + + /** + * 更新頂部狀態列的專案路徑顯示 + */ + SessionUIRenderer.prototype.updateTopProjectPathDisplay = function(sessionData) { + console.log('🎨 updateProjectPathDisplay 被調用:', sessionData); + + const projectPathElement = document.getElementById('projectPathDisplay'); + console.log('🎨 找到專案路徑元素:', !!projectPathElement); + + if (projectPathElement && sessionData.project_directory) { + const fullPath = sessionData.project_directory; + + // 使用工具函數截斷路徑 + const pathResult = window.MCPFeedback.Utils.truncatePathFromRight(fullPath, 2, 40); + console.log('🎨 路徑處理:', { fullPath, shortPath: pathResult.truncated }); + + // 更新顯示文字 + DOMUtils.safeSetTextContent(projectPathElement, pathResult.truncated); + + // 更新完整路徑屬性 + projectPathElement.setAttribute('data-full-path', fullPath); + + // 添加點擊複製功能(如果還沒有) + if (!projectPathElement.hasAttribute('data-copy-handler')) { + console.log('🎨 添加點擊複製功能'); + projectPathElement.setAttribute('data-copy-handler', 'true'); + projectPathElement.addEventListener('click', function() { + console.log('🎨 專案路徑被點擊'); + const fullPath = this.getAttribute('data-full-path'); + console.log('🎨 準備複製路徑:', fullPath); + + if (fullPath) { + const successMessage = window.i18nManager ? + window.i18nManager.t('app.pathCopied', '專案路徑已複製到剪貼板') : + '專案路徑已複製到剪貼板'; + const errorMessage = window.i18nManager ? + window.i18nManager.t('app.pathCopyFailed', '複製路徑失敗') : + '複製路徑失敗'; + + console.log('🎨 調用複製函數'); + window.MCPFeedback.Utils.copyToClipboard(fullPath, successMessage, errorMessage); + } + }); + } else { + console.log('🎨 點擊複製功能已存在'); + } + + // 添加 tooltip 位置自動調整 + this.adjustTooltipPosition(projectPathElement); + } + }; + + /** + * 調整 tooltip 位置以避免超出視窗邊界 + */ + SessionUIRenderer.prototype.adjustTooltipPosition = function(element) { + if (!element) return; + + // 移除之前的位置類別 + element.classList.remove('tooltip-up', 'tooltip-left', 'tooltip-right'); + + // 獲取元素位置 + const rect = element.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // 檢查是否需要調整垂直位置 + if (rect.bottom + 100 > viewportHeight) { + element.classList.add('tooltip-up'); + } + + // 檢查是否需要調整水平位置 + if (rect.left + 200 > viewportWidth) { + element.classList.add('tooltip-right'); + } else if (rect.left < 200) { + element.classList.add('tooltip-left'); + } }; /** @@ -168,10 +303,30 @@ console.log('🎨 更新會話狀態列:', sessionData); - // 更新當前會話 ID - 顯示完整 session ID + // 更新當前會話 ID - 顯示縮短版本,完整ID存在data-full-id中 const currentSessionElement = document.getElementById('currentSessionId'); if (currentSessionElement && sessionData.session_id) { - DOMUtils.safeSetTextContent(currentSessionElement, sessionData.session_id); + const shortId = sessionData.session_id.substring(0, 8) + '...'; + DOMUtils.safeSetTextContent(currentSessionElement, shortId); + currentSessionElement.setAttribute('data-full-id', sessionData.session_id); + + // 添加點擊複製功能(如果還沒有) + if (!currentSessionElement.hasAttribute('data-copy-handler')) { + currentSessionElement.setAttribute('data-copy-handler', 'true'); + currentSessionElement.addEventListener('click', function() { + const fullId = this.getAttribute('data-full-id'); + if (fullId) { + const successMessage = window.i18nManager ? + window.i18nManager.t('app.sessionIdCopied', '會話ID已複製到剪貼板') : + '會話ID已複製到剪貼板'; + const errorMessage = window.i18nManager ? + window.i18nManager.t('app.sessionIdCopyFailed', '複製會話ID失敗') : + '複製會話ID失敗'; + + window.MCPFeedback.Utils.copyToClipboard(fullId, successMessage, errorMessage); + } + }); + } } // 立即更新活躍時間(定時器會持續更新) diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/utils.js b/src/mcp_feedback_enhanced/web/static/js/modules/utils.js index 2baf154..1ff4376 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/utils.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/utils.js @@ -121,6 +121,125 @@ return document.querySelector(selector) !== null; }, + /** + * 從右側截斷路徑,保留最後幾個目錄層級 + * @param {string} path - 完整路徑 + * @param {number} maxLevels - 保留的最大目錄層級數(默認2) + * @param {number} maxLength - 最大顯示長度(默認40) + * @returns {object} 包含 truncated(截斷後的路徑)和 isTruncated(是否被截斷) + */ + truncatePathFromRight: function(path, maxLevels, maxLength) { + maxLevels = maxLevels || 2; + maxLength = maxLength || 40; + + if (!path || typeof path !== 'string') { + return { truncated: path || '', isTruncated: false }; + } + + // 如果路徑長度小於最大長度,直接返回 + if (path.length <= maxLength) { + return { truncated: path, isTruncated: false }; + } + + // 統一路徑分隔符為反斜線(Windows風格) + const normalizedPath = path.replace(/\//g, '\\'); + + // 分割路徑 + const parts = normalizedPath.split('\\').filter(part => part.length > 0); + + if (parts.length <= maxLevels) { + return { truncated: normalizedPath, isTruncated: false }; + } + + // 取最後幾個層級 + const lastParts = parts.slice(-maxLevels); + const truncatedPath = '...' + '\\' + lastParts.join('\\'); + + return { + truncated: truncatedPath, + isTruncated: true + }; + }, + + /** + * 複製文字到剪貼板(統一的複製功能) + * @param {string} text - 要複製的文字 + * @param {string} successMessage - 成功提示訊息 + * @param {string} errorMessage - 錯誤提示訊息 + * @returns {Promise} 複製是否成功 + */ + copyToClipboard: function(text, successMessage, errorMessage) { + successMessage = successMessage || '已複製到剪貼板'; + errorMessage = errorMessage || '複製失敗'; + + return new Promise(function(resolve) { + if (navigator.clipboard && navigator.clipboard.writeText) { + // 使用現代 Clipboard API + navigator.clipboard.writeText(text).then(function() { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } + resolve(true); + }).catch(function(err) { + console.error('Clipboard API 複製失敗:', err); + // 回退到舊方法 + const success = window.MCPFeedback.Utils.fallbackCopyToClipboard(text); + if (success) { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } + resolve(true); + } else { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(errorMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR); + } + resolve(false); + } + }); + } else { + // 直接使用回退方法 + const success = window.MCPFeedback.Utils.fallbackCopyToClipboard(text); + if (success) { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } + resolve(true); + } else { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(errorMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR); + } + resolve(false); + } + } + }); + }, + + /** + * 回退的複製到剪貼板方法 + * @param {string} text - 要複製的文字 + * @returns {boolean} 複製是否成功 + */ + fallbackCopyToClipboard: function(text) { + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + + return successful; + } catch (err) { + console.error('回退複製方法失敗:', err); + return false; + } + }, + /** * 安全的元素查詢 * @param {string} selector - CSS 選擇器 diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index b291997..6860100 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -398,12 +398,32 @@

MCP Feedback Enhanced

- 專案目錄: {{ project_directory }} + 📂 專案目錄: + {{ project_directory }}
+ +
+
+ + 📋 當前會話: + {{ session_id[:8] if session_id else 'loading' }}... + + + 活躍時間: -- + +
+
+
- -
- 連線時間: --:-- - 重連: 0 -
- - -
-
- 訊息: 0 - 延遲: --ms + +
+ +
+ 連線時間: --:-- + 重連: 0
-
- 會話: 1 - 狀態: 等待中 + + +
+
+ 訊息: 0 + 延遲: --ms +
+
+ 會話: 1 + 狀態: 等待中 +
@@ -530,20 +553,6 @@
- -
-
- - 📋 當前會話: {{ session_id[:8] if session_id else 'loading' }}... - - 活躍時間: -- -
-
- -
-
diff --git a/tests/helpers/manual_tooltip_test.html b/tests/helpers/manual_tooltip_test.html new file mode 100644 index 0000000..4f83c5d --- /dev/null +++ b/tests/helpers/manual_tooltip_test.html @@ -0,0 +1,288 @@ + + + + + + Tooltip 方向測試 + + + +
+

Tooltip 方向測試

+ + +
+ 📋 當前會話: + 6a674dda... + +
+ +
+

測試說明

+
+ 以下是不同位置的tooltip顯示測試。將滑鼠懸停在會話ID上查看tooltip的顯示方向。 +
+ +
+ ✅ 新版本 - 向下顯示 (推薦) + 6a674dda... +
+ +
+ ❌ 舊版本 - 向上顯示 (會超出畫面) + 6a674dda... +
+ +
+ ✅ 新版本的tooltip向下顯示,確保在頁面頂部也能完整看到完整的會話ID +
+
+ +
+

實際應用場景

+
+ 在實際應用中,會話ID位於頁面頂部的連線狀態欄中。向下顯示tooltip可以確保用戶始終能看到完整的會話ID,而不會被瀏覽器視窗邊界截斷。 +
+ +
+ + 💡 提示:將滑鼠懸停在上方頂部區域的會話ID上測試實際效果 + +
+
+
+ + + +