diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index 9de59e9..ba9e854 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -47,7 +47,10 @@ "processingFeedback": "Processing, please wait", "connectingMessage": "WebSocket connecting, feedback will be submitted automatically when connection is ready...", "invalidState": "Current state does not allow submission", - "sendFailed": "Send failed, please retry" + "sendFailed": "Send failed, please retry", + "noContent": "No content to copy", + "copySuccess": "Content copied to clipboard", + "copyFailed": "Failed to copy" }, "summary": { "title": "📋 AI Work Summary", @@ -321,7 +324,11 @@ "latencyMs": "Latency", "sessions": "Sessions", "reconnects": "Reconnects" - } + }, + "unknown": "Unknown" + }, + "stats": { + "detailedStats": "Detailed Statistics" }, "dynamic": { @@ -380,6 +387,7 @@ }, "about": { "title": "ℹ️ About", + "description": "A powerful MCP server that provides human-in-the-loop feedback functionality for AI-assisted development tools. Features a Web UI interface with rich capabilities including image upload, command execution, and multi-language support.", "appInfo": "Application Information", "version": "Version", "projectLinks": "Project Links", 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 9c339ab..0578ec8 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -47,7 +47,10 @@ "processingFeedback": "正在处理中,请稍候", "connectingMessage": "WebSocket 连接中,反馈将在连接就绪后自动提交...", "invalidState": "当前状态不允许提交", - "sendFailed": "发送失败,请重试" + "sendFailed": "发送失败,请重试", + "noContent": "没有可复制的内容", + "copySuccess": "内容已复制到剪贴板", + "copyFailed": "复制失败" }, "summary": { "title": "📋 AI 工作摘要", @@ -380,6 +383,7 @@ }, "about": { "title": "ℹ️ 关于", + "description": "一个强大的 MCP 服务器,为 AI 辅助开发工具提供人在回路的互动反馈功能。支持 Web UI 界面,并具备图片上传、命令执行、多语言等丰富功能。", "appInfo": "应用程序信息", "version": "版本", "projectLinks": "项目链接", @@ -463,5 +467,8 @@ "testPlaying": "正在播放测试音效", "audioNotFound": "找不到选择的音效" } + }, + "stats": { + "detailedStats": "详细统计信息" } } 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 60d7e3f..2e8e147 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -52,7 +52,10 @@ "processingFeedback": "正在處理中,請稍候", "connectingMessage": "WebSocket 連接中,回饋將在連接就緒後自動提交...", "invalidState": "當前狀態不允許提交", - "sendFailed": "發送失敗,請重試" + "sendFailed": "發送失敗,請重試", + "noContent": "沒有可複製的內容", + "copySuccess": "內容已複製到剪貼板", + "copyFailed": "複製失敗" }, "summary": { "title": "📋 AI 工作摘要", @@ -385,6 +388,7 @@ }, "about": { "title": "ℹ️ 關於", + "description": "一個強大的 MCP 伺服器,為 AI 輔助開發工具提供人在回路的互動回饋功能。支援 Web UI 介面,並具備圖片上傳、命令執行、多語言等豐富功能。", "appInfo": "應用程式資訊", "version": "版本", "projectLinks": "專案連結", @@ -468,5 +472,8 @@ "testPlaying": "正在播放測試音效", "audioNotFound": "找不到選擇的音效" } + }, + "stats": { + "detailedStats": "詳細統計資訊" } } 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 85e6d7d..5c91a40 100644 --- a/src/mcp_feedback_enhanced/web/static/css/session-management.css +++ b/src/mcp_feedback_enhanced/web/static/css/session-management.css @@ -51,6 +51,86 @@ gap: 20px; } +/* 緊湊版頂部欄 */ +.connection-monitor-bar.compact { + padding: 4px 12px; + gap: 0; + font-size: 12px; + height: 32px; + align-items: center; + justify-content: flex-start; +} + +/* 緊湊版元素樣式 */ +.app-title-compact { + font-weight: 600; + color: var(--text-primary); + font-size: 13px; + white-space: nowrap; +} + +.info-separator { + margin: 0 6px; + color: var(--text-secondary); + opacity: 0.4; + font-size: 10px; +} + +.project-info-compact, +.session-info-compact, +.countdown-display-compact, +.connection-status-compact { + display: flex; + align-items: center; + gap: 4px; + white-space: nowrap; + font-size: 11px; +} + +.project-info-compact .project-path-display { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 11px; +} + +.session-info-compact .session-id-display { + font-size: 11px; + padding: 1px 4px; +} + +.countdown-display-compact { + color: var(--warning-color); +} + +.countdown-display-compact .countdown-timer { + font-weight: bold; + font-size: 12px; +} + +.connection-status-compact { + margin-left: auto; +} + +.connection-status-compact .status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; +} + +.connection-status-compact.connected { + color: var(--status-connected); +} + +.connection-status-compact.disconnected { + color: var(--status-disconnected); +} + +.connection-status-compact.connecting { + color: var(--status-connecting); +} + /* 應用資訊區域 */ .app-info-section { display: flex; @@ -648,42 +728,37 @@ /* ===== 響應式設計 ===== */ @media (max-width: 768px) { - .connection-monitor-bar { - flex-direction: column; - gap: 8px; - padding: 12px 16px; + /* 緊湊版頂部欄在移動設備上的調整 */ + .connection-monitor-bar.compact { + padding: 4px 10px; + font-size: 11px; + overflow-x: auto; + white-space: nowrap; } - - .app-info-section { - width: 100%; - text-align: center; + + /* 隱藏專案路徑以節省空間 */ + .project-info-compact { + display: none; } - - .app-title { - justify-content: center; - flex-wrap: wrap; + + /* 隱藏相關的分隔符 */ + .project-info-compact + .info-separator { + display: none; } - - .app-title h1 { - font-size: 16px; + + /* 調整標題字體 */ + .app-title-compact { + font-size: 12px; } - - .connection-status-group { - width: 100%; - justify-content: center; - flex-wrap: wrap; + + /* 減少分隔符間距 */ + .info-separator { + margin: 0 6px; } - - .connection-status-combined { - width: 100%; - align-items: center; - } - - .detailed-status-info { - margin-left: 0; - margin-top: 0; - justify-content: center; - flex-wrap: wrap; + + /* 確保倒數計時器始終可見 */ + .countdown-display-compact { + font-weight: bold; } /* 會話管理頁籤響應式調整 */ @@ -697,6 +772,28 @@ } } +/* 超小螢幕調整 */ +@media (max-width: 480px) { + /* 隱藏會話 ID */ + .session-info-compact { + display: none; + } + + /* 隱藏相關的分隔符 */ + .session-info-compact + .info-separator { + display: none; + } + + /* 保留核心資訊:標題、倒數計時器、連線狀態 */ + .connection-monitor-bar.compact { + justify-content: space-between; + } + + .connection-status-compact { + margin-left: 0; + } +} + /* ===== 載入狀態 ===== */ .loading-skeleton { background: linear-gradient(90deg, @@ -1204,3 +1301,32 @@ transition-duration: 0.01ms !important; } } + +/* 響應式設計 - 緊湊版頂部欄 */ +@media (max-width: 768px) { + /* 小螢幕隱藏專案路徑 */ + .connection-monitor-bar.compact .project-info-compact { + display: none; + } + + /* 隱藏相鄰的分隔符 */ + .connection-monitor-bar.compact .project-info-compact + .info-separator { + display: none; + } +} + +@media (max-width: 480px) { + /* 極小螢幕只顯示最關鍵資訊 */ + .connection-monitor-bar.compact .session-info-compact { + display: none; + } + + .connection-monitor-bar.compact .session-info-compact + .info-separator { + display: none; + } + + /* 調整標題字體大小 */ + .app-title-compact { + font-size: 12px; + } +} diff --git a/src/mcp_feedback_enhanced/web/static/css/styles.css b/src/mcp_feedback_enhanced/web/static/css/styles.css index 64d1c45..8d8c80f 100644 --- a/src/mcp_feedback_enhanced/web/static/css/styles.css +++ b/src/mcp_feedback_enhanced/web/static/css/styles.css @@ -98,6 +98,114 @@ z-index: 1000; } +/* ===== 可折疊統計面板 ===== */ +.stats-panel-floating { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); + z-index: 100; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform; +} + +/* 收起狀態 */ +.stats-panel-floating.collapsed { + transform: translateY(calc(100% - 40px)); +} + +/* 統計面板頭部 */ +.stats-panel-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + cursor: pointer; + user-select: none; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); +} + +.stats-panel-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); +} + +.stats-toggle-icon { + transition: transform 0.3s ease; +} + +.stats-panel-floating.collapsed .stats-toggle-icon { + transform: rotate(180deg); +} + +/* 統計面板內容 */ +.stats-panel-content { + padding: 16px 20px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + max-width: 1200px; + margin: 0 auto; +} + +/* 統計項目 */ +.stats-item-detailed { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px; + background: var(--bg-primary); + border-radius: 6px; + border: 1px solid var(--border-color); +} + +.stats-item-label { + font-size: 11px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stats-item-value { + font-size: 16px; + font-weight: 600; + color: var(--accent-color); + font-family: 'Consolas', 'Monaco', monospace; +} + + +/* 動畫效能優化 */ +@media (prefers-reduced-motion: reduce) { + .stats-panel-floating { + transition: none; + } + + .stats-toggle-icon { + transition: none; + } +} + +/* 響應式設計 */ +@media (max-width: 768px) { + .stats-panel-content { + grid-template-columns: 1fr; + gap: 12px; + padding: 12px 16px; + } + + .stats-item-detailed { + padding: 10px; + } +} + .tooltip:hover::after { opacity: 1; } @@ -371,7 +479,7 @@ body { .container { width: 100%; margin: 0 auto; - padding: 20px; + padding: 12px; background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; @@ -950,7 +1058,7 @@ body { flex-direction: column; min-width: 0; min-height: 0; /* 允許內容自然收縮 */ - padding: 20px; + padding: 12px; overflow: hidden; transition: all 0.6s cubic-bezier(0.4, 0.0, 0.2, 1); background: var(--bg-primary); @@ -968,7 +1076,7 @@ body { /* 分頁樣式 */ .tabs { border-bottom: 2px solid var(--border-color); - margin-bottom: 20px; + margin-bottom: 12px; } .tab-buttons { @@ -980,7 +1088,7 @@ body { background: transparent; border: none; color: var(--text-secondary); - padding: 12px 20px; + padding: 10px 16px; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.3s ease; diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 1c0658e..8c848c4 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -314,6 +314,15 @@ }); } + // 複製用戶內容按鈕 + const copyUserFeedback = window.MCPFeedback.Utils.safeQuerySelector('#copyUserFeedback'); + if (copyUserFeedback) { + copyUserFeedback.addEventListener('click', function(e) { + e.preventDefault(); + self.copyUserFeedback(); + }); + } + // 快捷鍵 document.addEventListener('keydown', function(e) { // Ctrl+Enter 提交回饋 @@ -1058,8 +1067,14 @@ }); if (success) { - // 清空表單 - this.clearFeedback(); + // 重置表單狀態但保留文字內容 + if (this.uiManager) { + this.uiManager.resetFeedbackForm(false); // false 表示不清空文字 + } + // 只清空圖片 + if (this.imageHandler) { + this.imageHandler.clearImages(); + } console.log('📤 回饋已發送,等待服務器確認...'); } else { throw new Error('WebSocket 發送失敗'); @@ -1121,9 +1136,9 @@ FeedbackApp.prototype.clearFeedback = function() { console.log('🧹 清空回饋內容...'); - // 使用 UI 管理器重置表單 + // 使用 UI 管理器重置表單,並清空文字 if (this.uiManager) { - this.uiManager.resetFeedbackForm(); + this.uiManager.resetFeedbackForm(true); // 傳入 true 表示要清空文字 } // 清空圖片數據 @@ -1134,6 +1149,57 @@ console.log('✅ 回饋內容清空完成'); }; + /** + * 複製用戶回饋內容 + */ + FeedbackApp.prototype.copyUserFeedback = function() { + console.log('📋 複製用戶回饋內容...'); + + const feedbackInput = window.MCPFeedback.Utils.safeQuerySelector('#combinedFeedbackText'); + if (!feedbackInput || !feedbackInput.value.trim()) { + window.MCPFeedback.Utils.showMessage( + window.i18nManager ? window.i18nManager.t('feedback.noContent') : '沒有可複製的內容', + window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING + ); + return; + } + + const textContent = feedbackInput.value; + + // 複製到剪貼板 + navigator.clipboard.writeText(textContent) + .then(function() { + console.log('✅ 內容已複製到剪貼板'); + window.MCPFeedback.Utils.showMessage( + window.i18nManager ? window.i18nManager.t('feedback.copySuccess') : '內容已複製到剪貼板', + window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS + ); + }) + .catch(function(err) { + console.error('❌ 複製失敗:', err); + // 降級方案:使用舊的複製方法 + const textarea = document.createElement('textarea'); + textarea.value = textContent; + textarea.style.position = 'fixed'; + textarea.style.left = '-999999px'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + window.MCPFeedback.Utils.showMessage( + window.i18nManager ? window.i18nManager.t('feedback.copySuccess') : '內容已複製到剪貼板', + window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS + ); + } catch (error) { + window.MCPFeedback.Utils.showMessage( + window.i18nManager ? window.i18nManager.t('feedback.copyFailed') : '複製失敗', + window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR + ); + } + document.body.removeChild(textarea); + }); + }; + /** * 取消回饋 */ @@ -1298,7 +1364,7 @@ if (self.uiManager) { // console.log('🔧 準備更新 AI 摘要內容,summary 長度:', sessionData.summary ? sessionData.summary.length : 'undefined'); self.uiManager.updateAISummaryContent(sessionData.summary); - self.uiManager.resetFeedbackForm(); + self.uiManager.resetFeedbackForm(false); // 不清空文字內容 self.uiManager.updateStatusIndicator(); } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/connection-monitor.js b/src/mcp_feedback_enhanced/web/static/js/modules/connection-monitor.js index 92eed5b..c4951c5 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/connection-monitor.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/connection-monitor.js @@ -133,6 +133,36 @@ indicator.className = 'connection-indicator ' + status; } + // 更新精簡的頂部狀態指示器(現在是緊湊版) + const minimalIndicator = document.getElementById('connectionStatusMinimal'); + if (minimalIndicator) { + minimalIndicator.className = 'connection-status-compact ' + status; + const statusText = minimalIndicator.querySelector('.status-text'); + if (statusText) { + let statusKey = ''; + switch (status) { + case 'connected': + statusKey = 'connectionMonitor.connected'; + break; + case 'connecting': + statusKey = 'connectionMonitor.connecting'; + break; + case 'disconnected': + statusKey = 'connectionMonitor.disconnected'; + break; + case 'reconnecting': + statusKey = 'connectionMonitor.reconnecting'; + break; + default: + statusKey = 'connectionMonitor.unknown'; + } + statusText.setAttribute('data-i18n', statusKey); + if (window.i18nManager) { + statusText.textContent = window.i18nManager.t(statusKey); + } + } + } + // 處理特殊狀態 switch (status) { case 'connected': @@ -282,15 +312,30 @@ } } + // 更新統計面板中的延遲顯示 + const statsLatency = document.getElementById('statsLatency'); + if (statsLatency) { + statsLatency.textContent = this.currentLatency > 0 ? this.currentLatency + 'ms' : '--ms'; + } + // 更新連線時間 - if (this.connectionTimeDisplay && this.connectionStartTime) { + let connectionTimeStr = '--:--'; + if (this.connectionStartTime) { const duration = Math.floor((Date.now() - this.connectionStartTime) / 1000); const minutes = Math.floor(duration / 60); const seconds = duration % 60; + connectionTimeStr = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); + } + + if (this.connectionTimeDisplay) { const connectionTimeLabel = window.i18nManager ? window.i18nManager.t('connectionMonitor.connectionTime') : '連線時間'; - this.connectionTimeDisplay.textContent = connectionTimeLabel + ': ' + - String(minutes).padStart(2, '0') + ':' + - String(seconds).padStart(2, '0'); + this.connectionTimeDisplay.textContent = connectionTimeLabel + ': ' + connectionTimeStr; + } + + // 更新統計面板中的連線時間 + const statsConnectionTime = document.getElementById('statsConnectionTime'); + if (statsConnectionTime) { + statsConnectionTime.textContent = connectionTimeStr; } // 更新重連次數 @@ -300,10 +345,35 @@ this.reconnectCountDisplay.textContent = reconnectLabel + ': ' + this.reconnectCount + ' ' + timesLabel; } + // 更新統計面板中的重連次數 + const statsReconnectCount = document.getElementById('statsReconnectCount'); + if (statsReconnectCount) { + statsReconnectCount.textContent = this.reconnectCount.toString(); + } + // 更新訊息計數 if (this.messageCountDisplay) { this.messageCountDisplay.textContent = this.messageCount; } + + // 更新統計面板中的訊息計數 + const statsMessageCount = document.getElementById('statsMessageCount'); + if (statsMessageCount) { + statsMessageCount.textContent = this.messageCount.toString(); + } + + // 更新統計面板中的會話數和狀態 + const sessionCount = document.getElementById('sessionCount'); + const statsSessionCount = document.getElementById('statsSessionCount'); + if (sessionCount && statsSessionCount) { + statsSessionCount.textContent = sessionCount.textContent; + } + + const sessionStatusText = document.getElementById('sessionStatusText'); + const statsSessionStatus = document.getElementById('statsSessionStatus'); + if (sessionStatusText && statsSessionStatus) { + statsSessionStatus.textContent = sessionStatusText.textContent; + } }; /** diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js index 15bc62c..534b55d 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js @@ -482,14 +482,18 @@ /** * 重置回饋表單 + * @param {boolean} clearText - 是否清空文字內容,預設為 false */ - UIManager.prototype.resetFeedbackForm = function() { + UIManager.prototype.resetFeedbackForm = function(clearText) { console.log('🔄 重置回饋表單...'); - // 清空回饋輸入 + // 根據參數決定是否清空回饋輸入 const feedbackInput = Utils.safeQuerySelector('#combinedFeedbackText'); if (feedbackInput) { - feedbackInput.value = ''; + if (clearText === true) { + feedbackInput.value = ''; + console.log('📝 已清空文字內容'); + } feedbackInput.disabled = false; } diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 92c09a4..ee2efee 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -396,85 +396,51 @@ - -
- -
-
-

MCP Feedback Enhanced

-
-
- 📂 專案目錄: - {{ project_directory }} -
+ +
+ +
+ MCP Feedback
- -
- -
-
- - 📋 當前會話: - {{ session_id[:8] if session_id else 'loading' }}... - - - 活躍時間: -- - -
-
+ + · - - - - -
-
- 連接中... -
-
延遲: --ms
-
-
-
-
-
-
-
- - -
- -
- 連線時間: --:-- - 重連: 0 -
- - -
-
- 訊息: 0 - 延遲: --ms -
-
- 會話: 1 - 狀態: 等待中 -
-
-
+ +
+ 📂 + {{ project_directory[-30:] if project_directory|length > 30 else project_directory }}
- -
- + + · + + +
+ 📋 + {{ session_id[:6] if session_id else '--' }} +
+ + + + + + · + + +
+ + 已連線
@@ -517,10 +483,6 @@
-
- 以下是 AI 助手完成的工作摘要,請仔細查看並提供您的回饋意見。 -
-
{{ summary }} @@ -579,29 +541,9 @@
- - {% set id = "combinedFeedbackStatusIndicator" %} - {% set status = "waiting" %} - {% set icon = "⏳" %} - {% set title = "等待回饋" %} - {% set message = "請提供您的回饋意見" %} - {% include 'components/status-indicator.html' %} -
- -
- - -
- + + +
+ + + +
@@ -998,7 +956,6 @@
- 一個強大的 MCP 伺服器,為 AI 輔助開發工具提供人在回路的互動回饋功能。支援 Web UI 介面,並具備圖片上傳、命令執行、多語言等豐富功能。
@@ -1203,5 +1160,53 @@ initializeApp(); } + + + + +