diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index 0ccc010..b3d4f88 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -224,6 +224,28 @@ "unknown": "Unknown" } }, + "sessionHistory": { + "management": { + "title": "Session History Management", + "retentionPeriod": "Retention Period", + "retentionHours": "hours", + "export": "Export", + "clear": "Clear", + "exportAll": "Export All", + "exportSingle": "Export This Session", + "confirmClear": "Are you sure you want to clear all session history?", + "exportSuccess": "Session history exported successfully", + "clearSuccess": "Session history cleared successfully", + "description": "Manage locally stored session history records, including retention period settings and data export functionality" + }, + "retention": { + "24hours": "24 hours", + "72hours": "72 hours", + "168hours": "7 days", + "720hours": "30 days", + "custom": "Custom" + } + }, "connectionMonitor": { "connecting": "Connecting...", "connected": "Connected", 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 5f8a3ea..0724442 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -224,6 +224,28 @@ "unknown": "未知" } }, + "sessionHistory": { + "management": { + "title": "会话历史管理", + "retentionPeriod": "保存期限", + "retentionHours": "小时", + "export": "导出", + "clear": "清空", + "exportAll": "导出全部", + "exportSingle": "导出此会话", + "confirmClear": "确定要清空所有会话历史吗?", + "exportSuccess": "会话历史已导出", + "clearSuccess": "会话历史已清空", + "description": "管理本地存储的会话历史记录,包括保存期限设定和数据导出功能" + }, + "retention": { + "24hours": "24 小时", + "72hours": "72 小时", + "168hours": "7 天", + "720hours": "30 天", + "custom": "自定义" + } + }, "connectionMonitor": { "connecting": "连接中...", "connected": "已连接", 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 64d8995..559800a 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -229,6 +229,28 @@ }, "noSummary": "無摘要" }, + "sessionHistory": { + "management": { + "title": "會話歷史管理", + "retentionPeriod": "保存期限", + "retentionHours": "小時", + "export": "匯出", + "clear": "清空", + "exportAll": "匯出全部", + "exportSingle": "匯出此會話", + "confirmClear": "確定要清空所有會話歷史嗎?", + "exportSuccess": "會話歷史已匯出", + "clearSuccess": "會話歷史已清空", + "description": "管理本地儲存的會話歷史記錄,包括保存期限設定和資料匯出功能" + }, + "retention": { + "24hours": "24 小時", + "72hours": "72 小時", + "168hours": "7 天", + "720hours": "30 天", + "custom": "自訂" + } + }, "connectionMonitor": { "connecting": "連接中...", "connected": "已連接", 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 5af885a..ee81708 100644 --- a/src/mcp_feedback_enhanced/web/static/css/session-management.css +++ b/src/mcp_feedback_enhanced/web/static/css/session-management.css @@ -539,6 +539,40 @@ border-color: var(--accent-color); } +/* 匯出按鈕樣式 */ +.btn-export { + background: var(--bg-primary) !important; + border-color: var(--success-color) !important; + color: var(--success-color) !important; +} + +.btn-export:hover { + background: var(--success-color) !important; + color: white !important; + border-color: var(--success-color) !important; +} + +/* 會話歷史管理按鈕樣式 */ +#exportSessionHistoryBtn { + background: var(--success-color); + border-color: var(--success-color); + color: white; +} + +#exportSessionHistoryBtn:hover { + background: var(--success-color-dark, #28a745); + border-color: var(--success-color-dark, #28a745); +} + +#clearSessionHistoryBtn { + background: var(--bg-primary); +} + +#clearSessionHistoryBtn:hover { + background: var(--error-color); + color: white; +} + /* ===== 會話統計 ===== */ .session-stats-section { margin-top: 16px; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js index 76b3219..c4befb7 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js @@ -73,6 +73,7 @@ // 最後初始化數據管理器(確保 UI 組件已準備好接收回調) this.dataManager = new window.MCPFeedback.Session.DataManager({ + settingsManager: this.settingsManager, onSessionChange: function(sessionData) { self.handleSessionChange(sessionData); }, @@ -569,6 +570,97 @@ this.uiRenderer.renderStats(stats); }; + /** + * 匯出會話歷史 + */ + SessionManager.prototype.exportSessionHistory = function() { + if (!this.dataManager) { + console.error('📋 DataManager 未初始化'); + return; + } + + try { + const filename = this.dataManager.exportSessionHistory(); + + // 顯示成功訊息 + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.exportSuccess') : + '會話歷史已匯出'; + window.MCPFeedback.Utils.showMessage(message + ': ' + filename, 'success'); + } + } catch (error) { + console.error('📋 匯出會話歷史失敗:', error); + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error'); + } + } + }; + + /** + * 匯出單一會話 + */ + SessionManager.prototype.exportSingleSession = function(sessionId) { + if (!this.dataManager) { + console.error('📋 DataManager 未初始化'); + return; + } + + try { + const filename = this.dataManager.exportSingleSession(sessionId); + if (filename) { + // 顯示成功訊息 + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.exportSuccess') : + '會話已匯出'; + window.MCPFeedback.Utils.showMessage(message + ': ' + filename, 'success'); + } + } + } catch (error) { + console.error('📋 匯出單一會話失敗:', error); + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error'); + } + } + }; + + /** + * 清空會話歷史 + */ + SessionManager.prototype.clearSessionHistory = function() { + if (!this.dataManager) { + console.error('📋 DataManager 未初始化'); + return; + } + + // 確認對話框 + const confirmMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.confirmClear') : + '確定要清空所有會話歷史嗎?'; + + if (!confirm(confirmMessage)) { + return; + } + + try { + this.dataManager.clearHistory(); + + // 顯示成功訊息 + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.clearSuccess') : + '會話歷史已清空'; + window.MCPFeedback.Utils.showMessage(message, 'success'); + } + } catch (error) { + console.error('📋 清空會話歷史失敗:', error); + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage('清空失敗: ' + error.message, 'error'); + } + } + }; + /** * 清理資源 */ @@ -618,6 +710,33 @@ } }; + // 全域匯出會話歷史方法 + window.MCPFeedback.SessionManager.exportSessionHistory = function() { + if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) { + window.MCPFeedback.app.sessionManager.exportSessionHistory(); + } else { + console.warn('找不到 SessionManager 實例'); + } + }; + + // 全域匯出單一會話方法 + window.MCPFeedback.SessionManager.exportSingleSession = function(sessionId) { + if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) { + window.MCPFeedback.app.sessionManager.exportSingleSession(sessionId); + } else { + console.warn('找不到 SessionManager 實例'); + } + }; + + // 全域清空會話歷史方法 + window.MCPFeedback.SessionManager.clearSessionHistory = function() { + if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) { + window.MCPFeedback.app.sessionManager.clearSessionHistory(); + } else { + console.warn('找不到 SessionManager 實例'); + } + }; + console.log('✅ SessionManager (重構版) 模組載入完成'); })(); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js index 7e94977..ffc6d66 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js @@ -33,12 +33,18 @@ totalSessions: 0 }; + // localStorage 相關設定 + this.localStorageKey = 'mcp-session-history'; + this.settingsManager = options.settingsManager || null; + // 回調函數 this.onSessionChange = options.onSessionChange || null; this.onHistoryChange = options.onHistoryChange || null; this.onStatsChange = options.onStatsChange || null; - // 初始化統計資訊 + // 初始化:載入歷史記錄並清理過期資料 + this.loadFromLocalStorage(); + this.cleanupExpiredSessions(); this.updateStats(); console.log('📊 SessionDataManager 初始化完成'); @@ -196,6 +202,9 @@ return false; } + // 新增儲存時間戳記 + sessionData.saved_at = TimeUtils.getCurrentTimestamp(); + // 避免重複新增 const existingIndex = this.sessionHistory.findIndex(s => s.session_id === sessionData.session_id); if (existingIndex !== -1) { @@ -209,6 +218,9 @@ this.sessionHistory = this.sessionHistory.slice(0, 10); } + // 保存到 localStorage + this.saveToLocalStorage(); + this.updateStats(); // 觸發回調 @@ -295,6 +307,10 @@ */ SessionDataManager.prototype.clearHistory = function() { this.sessionHistory = []; + + // 清空 localStorage + this.clearLocalStorage(); + this.updateStats(); if (this.onHistoryChange) { this.onHistoryChange(this.sessionHistory); @@ -358,6 +374,191 @@ return '暫無摘要'; }; + /** + * 從 localStorage 載入會話歷史 + */ + SessionDataManager.prototype.loadFromLocalStorage = function() { + if (!window.localStorage) { + console.warn('📊 localStorage 不可用'); + return; + } + + try { + const stored = localStorage.getItem(this.localStorageKey); + if (stored) { + const data = JSON.parse(stored); + if (data && Array.isArray(data.sessions)) { + this.sessionHistory = data.sessions; + console.log('📊 從 localStorage 載入', this.sessionHistory.length, '個會話'); + } + } + } catch (error) { + console.error('📊 從 localStorage 載入會話歷史失敗:', error); + } + }; + + /** + * 保存會話歷史到 localStorage + */ + SessionDataManager.prototype.saveToLocalStorage = function() { + if (!window.localStorage) { + console.warn('📊 localStorage 不可用'); + return; + } + + try { + const data = { + sessions: this.sessionHistory, + lastCleanup: TimeUtils.getCurrentTimestamp() + }; + localStorage.setItem(this.localStorageKey, JSON.stringify(data)); + console.log('📊 已保存', this.sessionHistory.length, '個會話到 localStorage'); + } catch (error) { + console.error('📊 保存會話歷史到 localStorage 失敗:', error); + } + }; + + /** + * 清空 localStorage 中的會話歷史 + */ + SessionDataManager.prototype.clearLocalStorage = function() { + if (!window.localStorage) { + return; + } + + try { + localStorage.removeItem(this.localStorageKey); + console.log('📊 已清空 localStorage 中的會話歷史'); + } catch (error) { + console.error('📊 清空 localStorage 失敗:', error); + } + }; + + /** + * 清理過期的會話 + */ + SessionDataManager.prototype.cleanupExpiredSessions = function() { + if (!this.settingsManager) { + return; + } + + const retentionHours = this.settingsManager.get('sessionHistoryRetentionHours', 72); + const retentionMs = retentionHours * 60 * 60 * 1000; + const now = TimeUtils.getCurrentTimestamp(); + + const originalCount = this.sessionHistory.length; + this.sessionHistory = this.sessionHistory.filter(function(session) { + const sessionAge = now - (session.saved_at || session.completed_at || session.created_at || 0); + return sessionAge < retentionMs; + }); + + const cleanedCount = originalCount - this.sessionHistory.length; + if (cleanedCount > 0) { + console.log('📊 清理了', cleanedCount, '個過期會話'); + this.saveToLocalStorage(); + } + }; + + /** + * 檢查會話是否過期 + */ + SessionDataManager.prototype.isSessionExpired = function(session) { + if (!this.settingsManager) { + return false; + } + + const retentionHours = this.settingsManager.get('sessionHistoryRetentionHours', 72); + const retentionMs = retentionHours * 60 * 60 * 1000; + const now = TimeUtils.getCurrentTimestamp(); + const sessionTime = session.saved_at || session.completed_at || session.created_at || 0; + + return (now - sessionTime) > retentionMs; + }; + + /** + * 匯出會話歷史 + */ + SessionDataManager.prototype.exportSessionHistory = function() { + const exportData = { + exportedAt: new Date().toISOString(), + totalSessions: this.sessionHistory.length, + sessions: this.sessionHistory.map(function(session) { + return { + session_id: session.session_id, + created_at: session.created_at, + completed_at: session.completed_at, + duration: session.duration, + status: session.status, + project_directory: session.project_directory, + ai_summary: session.summary || session.ai_summary, + saved_at: session.saved_at + }; + }) + }; + + const filename = 'session-history-' + new Date().toISOString().split('T')[0] + '.json'; + this.downloadJSON(exportData, filename); + + console.log('📊 匯出了', this.sessionHistory.length, '個會話'); + return filename; + }; + + /** + * 匯出單一會話 + */ + SessionDataManager.prototype.exportSingleSession = function(sessionId) { + const session = this.sessionHistory.find(function(s) { + return s.session_id === sessionId; + }); + + if (!session) { + console.error('📊 找不到會話:', sessionId); + return null; + } + + const exportData = { + exportedAt: new Date().toISOString(), + session: { + session_id: session.session_id, + created_at: session.created_at, + completed_at: session.completed_at, + duration: session.duration, + status: session.status, + project_directory: session.project_directory, + ai_summary: session.summary || session.ai_summary, + saved_at: session.saved_at + } + }; + + const shortId = sessionId.substring(0, 8); + const filename = 'session-' + shortId + '-' + new Date().toISOString().split('T')[0] + '.json'; + this.downloadJSON(exportData, filename); + + console.log('📊 匯出會話:', sessionId); + return filename; + }; + + /** + * 下載 JSON 檔案 + */ + SessionDataManager.prototype.downloadJSON = function(data, filename) { + try { + const jsonString = JSON.stringify(data, null, 2); + const blob = new Blob([jsonString], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (error) { + console.error('📊 下載檔案失敗:', error); + } + }; + /** * 清理資源 */ 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 076e277..a1a21f6 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 @@ -328,19 +328,39 @@ (window.i18nManager ? window.i18nManager.t('sessionManagement.viewDetails') : '查看') : (window.i18nManager ? window.i18nManager.t('sessionManagement.viewDetails') : '詳細資訊'); - const button = DOMUtils.createElement('button', { + const viewButton = DOMUtils.createElement('button', { className: 'btn-small', textContent: buttonText }); - // 添加點擊事件 - DOMUtils.addEventListener(button, 'click', function() { + // 添加查看詳情點擊事件 + DOMUtils.addEventListener(viewButton, 'click', function() { if (window.MCPFeedback && window.MCPFeedback.SessionManager) { window.MCPFeedback.SessionManager.viewSessionDetails(sessionData.session_id); } }); - actions.appendChild(button); + actions.appendChild(viewButton); + + // 如果是歷史會話,新增匯出按鈕 + if (isHistory) { + const exportButton = DOMUtils.createElement('button', { + className: 'btn-small btn-export', + textContent: window.i18nManager ? window.i18nManager.t('sessionHistory.management.exportSingle') : '匯出', + style: 'margin-left: 4px; font-size: 11px; padding: 2px 6px;' + }); + + // 添加匯出點擊事件 + DOMUtils.addEventListener(exportButton, 'click', function(e) { + e.stopPropagation(); // 防止觸發父元素事件 + if (window.MCPFeedback && window.MCPFeedback.SessionManager) { + window.MCPFeedback.SessionManager.exportSingleSession(sessionData.session_id); + } + }); + + actions.appendChild(exportButton); + } + return actions; }; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js index 37b7bf8..94be7ef 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js @@ -35,7 +35,9 @@ audioNotificationEnabled: false, audioNotificationVolume: 50, selectedAudioId: 'default-beep', - customAudios: [] + customAudios: [], + // 會話歷史設定 + sessionHistoryRetentionHours: 72 }; // 當前設定 @@ -407,6 +409,9 @@ // 應用自動提交設定 this.applyAutoSubmitSettingsToUI(); + + // 應用會話歷史設定 + this.applySessionHistorySettings(); }; /** @@ -549,7 +554,20 @@ } }; + /** + * 應用會話歷史設定 + */ + SettingsManager.prototype.applySessionHistorySettings = function() { + // 更新會話歷史保存期限選擇器 + const sessionHistoryRetentionSelect = Utils.safeQuerySelector('#sessionHistoryRetentionHours'); + if (sessionHistoryRetentionSelect) { + sessionHistoryRetentionSelect.value = this.currentSettings.sessionHistoryRetentionHours.toString(); + } + console.log('會話歷史設定已應用到 UI:', { + retentionHours: this.currentSettings.sessionHistoryRetentionHours + }); + }; /** * 設置事件監聽器 @@ -731,6 +749,44 @@ }); } + // 會話歷史保存期限設定 + const sessionHistoryRetentionSelect = Utils.safeQuerySelector('#sessionHistoryRetentionHours'); + if (sessionHistoryRetentionSelect) { + sessionHistoryRetentionSelect.addEventListener('change', function(e) { + const hours = parseInt(e.target.value); + self.set('sessionHistoryRetentionHours', hours); + console.log('會話歷史保存期限已更新:', hours, '小時'); + + // 觸發清理過期會話 + if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) { + const sessionManager = window.MCPFeedback.app.sessionManager; + if (sessionManager.dataManager && sessionManager.dataManager.cleanupExpiredSessions) { + sessionManager.dataManager.cleanupExpiredSessions(); + } + } + }); + } + + // 會話歷史匯出按鈕 + const exportHistoryBtn = Utils.safeQuerySelector('#exportSessionHistoryBtn'); + if (exportHistoryBtn) { + exportHistoryBtn.addEventListener('click', function() { + if (window.MCPFeedback && window.MCPFeedback.SessionManager) { + window.MCPFeedback.SessionManager.exportSessionHistory(); + } + }); + } + + // 會話歷史清空按鈕 + const clearHistoryBtn = Utils.safeQuerySelector('#clearSessionHistoryBtn'); + if (clearHistoryBtn) { + clearHistoryBtn.addEventListener('click', function() { + if (window.MCPFeedback && window.MCPFeedback.SessionManager) { + window.MCPFeedback.SessionManager.clearSessionHistory(); + } + }); + } + // 重置設定 const resetBtn = Utils.safeQuerySelector('#resetSettingsBtn'); if (resetBtn) { diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 65261b0..f99df32 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -884,6 +884,48 @@ + +