From be573a9a95ad20b584ceec78a99972ae9619523b Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Sun, 22 Jun 2025 02:49:20 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20=E9=87=8D=E6=A7=8B=E4=BA=86?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=A9=9F=E5=88=B6=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=20localStorage=20=E7=9B=B8=E9=97=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/routes/main_routes.py | 143 +++++------ .../web/static/js/app.js | 9 +- .../web/static/js/i18n.js | 26 +- .../web/static/js/modules/logger.js | 74 +++++- .../web/static/js/modules/settings-manager.js | 142 ++--------- .../web/static/js/modules/tab-manager.js | 235 ------------------ .../web/static/js/modules/utils.js | 15 +- .../web/templates/index.html | 2 +- 8 files changed, 169 insertions(+), 477 deletions(-) delete mode 100644 src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index 507feac..9e0d989 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -330,7 +330,7 @@ def setup_routes(manager: "WebUIManager"): sessions = history_data if isinstance(history_data, list) else [] last_cleanup = 0 - # 回傳與 localStorage 格式相容的資料 + # 回傳會話歷史資料 return JSONResponse( content={"sessions": sessions, "lastCleanup": last_cleanup} ) @@ -364,11 +364,6 @@ def setup_routes(manager: "WebUIManager"): "savedAt": int(time.time() * 1000), # 當前時間戳 } - # 如果是首次儲存且有 localStorage 遷移標記 - if not history_file.exists() and data.get("migratedFrom") == "localStorage": - history_data["migratedFrom"] = "localStorage" - history_data["migratedAt"] = int(time.time() * 1000) - # 保存會話歷史到檔案 with open(history_file, "w", encoding="utf-8") as f: json.dump(history_data, f, ensure_ascii=False, indent=2) @@ -391,79 +386,82 @@ def setup_routes(manager: "WebUIManager"): content={"status": "error", "message": f"保存失敗: {e!s}"}, ) - @manager.app.get("/api/active-tabs") - async def get_active_tabs(): - """獲取活躍標籤頁信息 - 優先使用全局狀態""" - current_time = time.time() - expired_threshold = 60 + @manager.app.get("/api/log-level") + async def get_log_level(): + """獲取日誌等級設定""" + try: + # 使用統一的設定檔案路徑 + config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" + settings_file = config_dir / "ui_settings.json" - # 清理過期的全局標籤頁 - valid_global_tabs = {} - for tab_id, tab_info in manager.global_active_tabs.items(): - if current_time - tab_info.get("last_seen", 0) <= expired_threshold: - valid_global_tabs[tab_id] = tab_info + if settings_file.exists(): + with open(settings_file, encoding="utf-8") as f: + settings_data = json.load(f) + log_level = settings_data.get("logLevel", "INFO") + debug_log(f"從設定檔案載入日誌等級: {log_level}") + return JSONResponse(content={"logLevel": log_level}) + else: + # 預設日誌等級 + default_log_level = "INFO" + debug_log(f"使用預設日誌等級: {default_log_level}") + return JSONResponse(content={"logLevel": default_log_level}) - manager.global_active_tabs = valid_global_tabs + except Exception as e: + debug_log(f"獲取日誌等級失敗: {e}") + return JSONResponse( + status_code=500, + content={"error": f"獲取日誌等級失敗: {e!s}"}, + ) - # 如果有當前會話,也更新會話的標籤頁狀態 - current_session = manager.get_current_session() - if current_session: - # 合併會話標籤頁到全局(如果有的話) - session_tabs = getattr(current_session, "active_tabs", {}) - for tab_id, tab_info in session_tabs.items(): - if current_time - tab_info.get("last_seen", 0) <= expired_threshold: - valid_global_tabs[tab_id] = tab_info - - # 更新會話的活躍標籤頁 - current_session.active_tabs = valid_global_tabs.copy() - manager.global_active_tabs = valid_global_tabs - - return JSONResponse( - content={ - "has_session": current_session is not None, - "active_tabs": valid_global_tabs, - "count": len(valid_global_tabs), - } - ) - - @manager.app.post("/api/register-tab") - async def register_tab(request: Request): - """註冊新標籤頁""" + @manager.app.post("/api/log-level") + async def set_log_level(request: Request): + """設定日誌等級""" try: data = await request.json() - tab_id = data.get("tabId") + log_level = data.get("logLevel") - if not tab_id: - return JSONResponse(status_code=400, content={"error": "缺少 tabId"}) + if not log_level or log_level not in ["DEBUG", "INFO", "WARN", "ERROR"]: + return JSONResponse( + status_code=400, + content={ + "error": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一" + }, + ) - current_session = manager.get_current_session() - if not current_session: - return JSONResponse(status_code=404, content={"error": "沒有活躍會話"}) + # 使用統一的設定檔案路徑 + config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" + config_dir.mkdir(parents=True, exist_ok=True) + settings_file = config_dir / "ui_settings.json" - # 註冊標籤頁 - tab_info = { - "timestamp": time.time() * 1000, # 毫秒時間戳 - "last_seen": time.time(), - "registered_at": time.time(), - } + # 載入現有設定或創建新設定 + settings_data = {} + if settings_file.exists(): + with open(settings_file, encoding="utf-8") as f: + settings_data = json.load(f) - if not hasattr(current_session, "active_tabs"): - current_session.active_tabs = {} + # 更新日誌等級 + settings_data["logLevel"] = log_level - current_session.active_tabs[tab_id] = tab_info + # 保存設定到檔案 + with open(settings_file, "w", encoding="utf-8") as f: + json.dump(settings_data, f, ensure_ascii=False, indent=2) - # 同時更新全局標籤頁狀態 - manager.global_active_tabs[tab_id] = tab_info - - debug_log(f"標籤頁已註冊: {tab_id}") + debug_log(f"日誌等級已設定為: {log_level}") return JSONResponse( - content={"status": "success", "tabId": tab_id, "registered": True} + content={ + "status": "success", + "logLevel": log_level, + "message": "日誌等級已更新", + } ) except Exception as e: - debug_log(f"註冊標籤頁失敗: {e}") - return JSONResponse(status_code=500, content={"error": f"註冊失敗: {e!s}"}) + debug_log(f"設定日誌等級失敗: {e}") + return JSONResponse( + status_code=500, + content={"status": "error", "message": f"設定失敗: {e!s}"}, + ) async def handle_websocket_message(manager: "WebUIManager", session, data: dict): @@ -494,29 +492,14 @@ async def handle_websocket_message(manager: "WebUIManager", session, data: dict) debug_log(f"發送狀態更新失敗: {e}") elif message_type == "heartbeat": - # WebSocket 心跳處理 - tab_id = data.get("tabId", "unknown") - timestamp = data.get("timestamp", 0) - - tab_info = {"timestamp": timestamp, "last_seen": time.time()} - - # 更新會話的標籤頁信息 - if hasattr(session, "active_tabs"): - session.active_tabs[tab_id] = tab_info - else: - session.active_tabs = {tab_id: tab_info} - - # 同時更新全局標籤頁狀態 - manager.global_active_tabs[tab_id] = tab_info - + # WebSocket 心跳處理(簡化版) # 發送心跳回應 if session.websocket: try: await session.websocket.send_json( { "type": "heartbeat_response", - "tabId": tab_id, - "timestamp": timestamp, + "timestamp": data.get("timestamp", 0), } ) except Exception as e: diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 48b3ca5..76aa23b 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -168,7 +168,7 @@ // 3. 初始化 UI 管理器 self.uiManager = new window.MCPFeedback.UIManager({ - currentTab: settings.activeTab, + // 移除 activeTab - 頁籤切換無需持久化 layoutMode: settings.layoutMode, onTabChange: function(tabName) { self.handleTabChange(tabName); @@ -178,8 +178,7 @@ } }); - // 4. 初始化標籤頁管理器 - self.tabManager = new window.MCPFeedback.TabManager(); + // 5. 初始化連線監控器 self.connectionMonitor = new window.MCPFeedback.ConnectionMonitor({ @@ -401,8 +400,8 @@ this.imageHandler.reinitialize(layoutMode); } - // 保存當前頁籤設定 - this.settingsManager.set('activeTab', tabName); + // 移除頁籤狀態保存 - 頁籤切換無需持久化 + // this.settingsManager.set('activeTab', tabName); }; /** diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index 0fb7d0e..f257259 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -14,14 +14,7 @@ class I18nManager { } async init() { - // 從 localStorage 載入語言偏好 - const savedLanguage = localStorage.getItem('language'); - if (savedLanguage) { - this.currentLanguage = savedLanguage; - console.log(`i18nManager 從 localStorage 載入語言: ${savedLanguage}`); - } else { - console.log(`i18nManager 使用默認語言: ${this.currentLanguage}`); - } + console.log(`i18nManager 使用預設語言: ${this.currentLanguage}`); // 載入翻譯數據 await this.loadTranslations(); @@ -125,7 +118,6 @@ class I18nManager { console.log(`🔄 i18nManager.setLanguage() 被調用: ${this.currentLanguage} -> ${language}`); if (this.translations[language]) { this.currentLanguage = language; - localStorage.setItem('language', language); this.applyTranslations(); // 更新所有語言選擇器(包括現代化版本) @@ -302,10 +294,14 @@ class I18nManager { selector.removeEventListener('change', selector._i18nChangeHandler); } - // 添加新的事件監聽器 + // 添加新的事件監聽器 - 通過 SettingsManager 統一管理 selector._i18nChangeHandler = (e) => { console.log(`🔄 i18n select change event: ${e.target.value}`); - this.setLanguage(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); } @@ -325,9 +321,13 @@ class I18nManager { // 移除舊的事件監聽器(如果存在) option.removeEventListener('click', option._languageClickHandler); - // 添加新的點擊事件監聽器 + // 添加新的點擊事件監聽器 - 通過 SettingsManager 統一管理 option._languageClickHandler = () => { - this.setLanguage(lang); + if (window.feedbackApp && window.feedbackApp.settingsManager) { + window.feedbackApp.settingsManager.set('language', lang); + } else { + this.setLanguage(lang); + } }; option.addEventListener('click', option._languageClickHandler); }); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/logger.js b/src/mcp_feedback_enhanced/web/static/js/modules/logger.js index b532721..4ea9dc6 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/logger.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/logger.js @@ -311,29 +311,87 @@ if (urlLogLevel) { return urlLogLevel; } - - // 檢查 localStorage - const storedLevel = localStorage.getItem('mcp-log-level'); - if (storedLevel) { - return storedLevel; - } - + // 檢查是否為開發環境 if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { return LogLevel.DEBUG; } - + return LogLevel.INFO; } + // 從 API 載入日誌等級 + function loadLogLevelFromAPI() { + fetch('/api/log-level') + .then(function(response) { + if (response.ok) { + return response.json(); + } + throw new Error('載入日誌等級失敗: ' + response.status); + }) + .then(function(data) { + const apiLogLevel = data.logLevel; + if (apiLogLevel && Object.values(LogLevel).includes(apiLogLevel)) { + currentLogLevel = apiLogLevel; + console.log('📋 從 API 載入日誌等級:', apiLogLevel); + } + }) + .catch(function(error) { + console.warn('⚠️ 載入日誌等級失敗,使用預設值:', error); + }); + } + + // 保存日誌等級到 API + function saveLogLevelToAPI(logLevel) { + fetch('/api/log-level', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + logLevel: logLevel + }) + }) + .then(function(response) { + if (response.ok) { + return response.json(); + } + throw new Error('保存日誌等級失敗: ' + response.status); + }) + .then(function(data) { + console.log('📋 日誌等級已保存:', data.logLevel); + }) + .catch(function(error) { + console.warn('⚠️ 保存日誌等級失敗:', error); + }); + } + // 設置全域日誌等級 globalLogger.setLevel(detectLogLevel()); + // 頁面載入後從 API 載入日誌等級 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', loadLogLevelFromAPI); + } else { + loadLogLevelFromAPI(); + } + // 匯出到全域命名空間 window.MCPFeedback.Logger = Logger; window.MCPFeedback.LogLevel = LogLevel; window.MCPFeedback.logger = globalLogger; + // 匯出設定方法 + window.MCPFeedback.setLogLevel = function(logLevel) { + if (Object.values(LogLevel).includes(logLevel)) { + globalLogger.setLevel(logLevel); + saveLogLevelToAPI(logLevel); + console.log('📋 日誌等級已更新:', LogLevelNames[logLevel]); + } else { + console.warn('⚠️ 無效的日誌等級:', logLevel); + } + }; + console.log('✅ Logger 模組載入完成,當前等級:', LogLevelNames[globalLogger.getLevel()]); })(); 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 f636e2b..ce26c06 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 @@ -30,7 +30,7 @@ language: 'zh-TW', imageSizeLimit: 0, enableBase64Detail: false, - activeTab: 'combined', + // 移除 activeTab - 頁籤切換無需持久化 sessionPanelCollapsed: false, // 自動定時提交設定 autoSubmitEnabled: false, @@ -58,12 +58,7 @@ this.onLanguageChange = options.onLanguageChange || null; this.onAutoSubmitStateChange = options.onAutoSubmitStateChange || null; - // 防抖機制相關 - this.saveToServerDebounceTimer = null; - this.saveToServerDebounceDelay = options.saveDebounceDelay || 500; // 預設 500ms 防抖延遲 - this.pendingServerSave = false; - - console.log('✅ SettingsManager 建構函數初始化完成,防抖延遲:', this.saveToServerDebounceDelay + 'ms'); + console.log('✅ SettingsManager 建構函數初始化完成 - 即時保存模式'); } /** @@ -74,28 +69,16 @@ return new Promise(function(resolve, reject) { logger.info('開始載入設定...'); - - // 優先從伺服器端載入設定 + + // 只從伺服器端載入設定 self.loadFromServer() .then(function(serverSettings) { if (serverSettings && Object.keys(serverSettings).length > 0) { self.currentSettings = self.mergeSettings(self.defaultSettings, serverSettings); logger.info('從伺服器端載入設定成功:', self.currentSettings); - - // 同步到 localStorage - self.saveToLocalStorage(); - resolve(self.currentSettings); - } else { - // 回退到 localStorage - return self.loadFromLocalStorage(); - } - }) - .then(function(localSettings) { - if (localSettings) { - self.currentSettings = self.mergeSettings(self.defaultSettings, localSettings); - console.log('從 localStorage 載入設定:', self.currentSettings); } else { console.log('沒有找到設定,使用預設值'); + self.currentSettings = Utils.deepClone(self.defaultSettings); } resolve(self.currentSettings); }) @@ -125,27 +108,7 @@ }); }; - /** - * 從 localStorage 載入設定 - */ - SettingsManager.prototype.loadFromLocalStorage = function() { - if (!Utils.isLocalStorageSupported()) { - return Promise.resolve(null); - } - try { - const localSettings = localStorage.getItem('mcp-feedback-settings'); - if (localSettings) { - const parsed = Utils.safeJsonParse(localSettings, null); - console.log('從 localStorage 載入設定:', parsed); - return Promise.resolve(parsed); - } - } catch (error) { - console.warn('從 localStorage 載入設定失敗:', error); - } - - return Promise.resolve(null); - }; /** * 保存設定 @@ -157,10 +120,7 @@ logger.debug('保存設定:', this.currentSettings); - // 保存到 localStorage - this.saveToLocalStorage(); - - // 同步保存到伺服器端 + // 只保存到伺服器端 this.saveToServer(); // 觸發回調 @@ -171,39 +131,13 @@ return this.currentSettings; }; - /** - * 保存到 localStorage - */ - SettingsManager.prototype.saveToLocalStorage = function() { - if (!Utils.isLocalStorageSupported()) { - return; - } - try { - localStorage.setItem('mcp-feedback-settings', JSON.stringify(this.currentSettings)); - } catch (error) { - console.error('保存設定到 localStorage 失敗:', error); - } - }; /** - * 保存到伺服器(帶防抖機制) + * 保存到伺服器(即時保存) */ SettingsManager.prototype.saveToServer = function() { - const self = this; - - // 清除之前的定時器 - if (self.saveToServerDebounceTimer) { - clearTimeout(self.saveToServerDebounceTimer); - } - - // 標記有待處理的保存操作 - self.pendingServerSave = true; - - // 設置新的防抖定時器 - self.saveToServerDebounceTimer = setTimeout(function() { - self._performServerSave(); - }, self.saveToServerDebounceDelay); + this._performServerSave(); }; /** @@ -212,12 +146,6 @@ SettingsManager.prototype._performServerSave = function() { const self = this; - if (!self.pendingServerSave) { - return; - } - - self.pendingServerSave = false; - fetch('/api/save-settings', { method: 'POST', headers: { @@ -227,7 +155,7 @@ }) .then(function(response) { if (response.ok) { - console.log('設定已同步到伺服器端'); + console.log('設定已即時同步到伺服器端'); } else { console.warn('同步設定到伺服器端失敗:', response.status); } @@ -237,20 +165,7 @@ }); }; - /** - * 立即保存到伺服器(跳過防抖機制) - * 用於重要操作,如語言變更、重置設定等 - */ - SettingsManager.prototype.saveToServerImmediate = function() { - // 清除防抖定時器 - if (this.saveToServerDebounceTimer) { - clearTimeout(this.saveToServerDebounceTimer); - this.saveToServerDebounceTimer = null; - } - // 立即執行保存 - this._performServerSave(); - }; /** * 合併設定 @@ -283,23 +198,15 @@ SettingsManager.prototype.set = function(key, value) { const oldValue = this.currentSettings[key]; this.currentSettings[key] = value; - + // 特殊處理語言變更 if (key === 'language' && oldValue !== value) { this.handleLanguageChange(value); - // 語言變更是重要操作,立即保存 - this.saveToLocalStorage(); - this.saveToServerImmediate(); - - // 觸發回調 - if (this.onSettingsChange) { - this.onSettingsChange(this.currentSettings); - } - } else { - // 一般設定變更使用防抖保存 - this.saveSettings(); } + // 所有設定變更都即時保存 + this.saveSettings(); + return this; }; @@ -334,14 +241,13 @@ SettingsManager.prototype.handleLanguageChange = function(newLanguage) { console.log('語言設定變更: ' + newLanguage); - // 同步到 localStorage - if (Utils.isLocalStorageSupported()) { - localStorage.setItem('language', newLanguage); - } - - // 通知國際化系統 + // 通知國際化系統(統一由 SettingsManager 管理) if (window.i18nManager) { - window.i18nManager.setLanguage(newLanguage); + // 直接設定語言,不觸發 i18nManager 的保存邏輯 + window.i18nManager.currentLanguage = newLanguage; + window.i18nManager.applyTranslations(); + window.i18nManager.setupLanguageSelectors(); + document.documentElement.lang = newLanguage; } // 延遲更新動態文字,確保 i18n 已經載入新語言 @@ -361,17 +267,11 @@ SettingsManager.prototype.resetSettings = function() { console.log('重置所有設定'); - // 清除 localStorage - if (Utils.isLocalStorageSupported()) { - localStorage.removeItem('mcp-feedback-settings'); - } - // 重置為預設值 this.currentSettings = Utils.deepClone(this.defaultSettings); - // 立即保存重置後的設定(重要操作) - this.saveToLocalStorage(); - this.saveToServerImmediate(); + // 立即保存重置後的設定到伺服器 + this.saveToServer(); // 觸發回調 if (this.onSettingsChange) { diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js deleted file mode 100644 index 50319a9..0000000 --- a/src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * MCP Feedback Enhanced - 標籤頁管理模組 - * ==================================== - * - * 處理多標籤頁狀態同步和智能瀏覽器管理 - */ - -(function() { - 'use strict'; - - // 確保命名空間和依賴存在 - window.MCPFeedback = window.MCPFeedback || {}; - const Utils = window.MCPFeedback.Utils; - - /** - * 標籤頁管理器建構函數 - */ - function TabManager() { - this.tabId = Utils.generateId('tab'); - this.heartbeatInterval = null; - this.heartbeatFrequency = Utils.CONSTANTS.DEFAULT_TAB_HEARTBEAT_FREQUENCY; - this.storageKey = 'mcp_feedback_tabs'; - this.lastActivityKey = 'mcp_feedback_last_activity'; - - this.init(); - } - - /** - * 初始化標籤頁管理器 - */ - TabManager.prototype.init = function() { - // 註冊當前標籤頁 - this.registerTab(); - - // 向服務器註冊標籤頁 - this.registerTabToServer(); - - // 開始心跳 - this.startHeartbeat(); - - // 監聽頁面關閉事件 - const self = this; - window.addEventListener('beforeunload', function() { - self.unregisterTab(); - }); - - // 監聽 localStorage 變化(其他標籤頁的狀態變化) - window.addEventListener('storage', function(e) { - if (e.key === self.storageKey) { - self.handleTabsChange(); - } - }); - - console.log('📋 TabManager 初始化完成,標籤頁 ID: ' + this.tabId); - }; - - /** - * 註冊當前標籤頁 - */ - TabManager.prototype.registerTab = function() { - const tabs = this.getActiveTabs(); - tabs[this.tabId] = { - timestamp: Date.now(), - url: window.location.href, - active: true - }; - - if (Utils.isLocalStorageSupported()) { - localStorage.setItem(this.storageKey, JSON.stringify(tabs)); - } - - this.updateLastActivity(); - console.log('✅ 標籤頁已註冊: ' + this.tabId); - }; - - /** - * 註銷當前標籤頁 - */ - TabManager.prototype.unregisterTab = function() { - const tabs = this.getActiveTabs(); - delete tabs[this.tabId]; - - if (Utils.isLocalStorageSupported()) { - localStorage.setItem(this.storageKey, JSON.stringify(tabs)); - } - - console.log('❌ 標籤頁已註銷: ' + this.tabId); - }; - - /** - * 開始心跳 - */ - TabManager.prototype.startHeartbeat = function() { - const self = this; - this.heartbeatInterval = setInterval(function() { - self.sendHeartbeat(); - }, this.heartbeatFrequency); - }; - - /** - * 發送心跳 - */ - TabManager.prototype.sendHeartbeat = function() { - const tabs = this.getActiveTabs(); - if (tabs[this.tabId]) { - tabs[this.tabId].timestamp = Date.now(); - - if (Utils.isLocalStorageSupported()) { - localStorage.setItem(this.storageKey, JSON.stringify(tabs)); - } - - this.updateLastActivity(); - } - }; - - /** - * 更新最後活動時間 - */ - TabManager.prototype.updateLastActivity = function() { - if (Utils.isLocalStorageSupported()) { - localStorage.setItem(this.lastActivityKey, Date.now().toString()); - } - }; - - /** - * 獲取活躍標籤頁 - */ - TabManager.prototype.getActiveTabs = function() { - if (!Utils.isLocalStorageSupported()) { - return {}; - } - - try { - const stored = localStorage.getItem(this.storageKey); - const tabs = stored ? Utils.safeJsonParse(stored, {}) : {}; - - // 清理過期的標籤頁 - const now = Date.now(); - const expiredThreshold = Utils.CONSTANTS.TAB_EXPIRED_THRESHOLD; - - for (const tabId in tabs) { - if (tabs.hasOwnProperty(tabId)) { - if (now - tabs[tabId].timestamp > expiredThreshold) { - delete tabs[tabId]; - } - } - } - - return tabs; - } catch (error) { - console.error('獲取活躍標籤頁失敗:', error); - return {}; - } - }; - - /** - * 檢查是否有活躍標籤頁 - */ - TabManager.prototype.hasActiveTabs = function() { - const tabs = this.getActiveTabs(); - return Object.keys(tabs).length > 0; - }; - - /** - * 檢查是否為唯一活躍標籤頁 - */ - TabManager.prototype.isOnlyActiveTab = function() { - const tabs = this.getActiveTabs(); - return Object.keys(tabs).length === 1 && tabs[this.tabId]; - }; - - /** - * 處理其他標籤頁狀態變化 - */ - TabManager.prototype.handleTabsChange = function() { - console.log('🔄 檢測到其他標籤頁狀態變化'); - // 可以在這裡添加更多邏輯 - }; - - /** - * 向服務器註冊標籤頁 - */ - TabManager.prototype.registerTabToServer = function() { - const self = this; - - fetch('/api/register-tab', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - tabId: this.tabId - }) - }) - .then(function(response) { - if (response.ok) { - return response.json(); - } else { - console.warn('⚠️ 標籤頁服務器註冊失敗: ' + response.status); - } - }) - .then(function(data) { - if (data) { - console.log('✅ 標籤頁已向服務器註冊: ' + self.tabId); - } - }) - .catch(function(error) { - console.warn('⚠️ 標籤頁服務器註冊錯誤: ' + error); - }); - }; - - /** - * 清理資源 - */ - TabManager.prototype.cleanup = function() { - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - this.unregisterTab(); - }; - - /** - * 獲取當前標籤頁 ID - */ - TabManager.prototype.getTabId = function() { - return this.tabId; - }; - - // 將 TabManager 加入命名空間 - window.MCPFeedback.TabManager = TabManager; - - console.log('✅ TabManager 模組載入完成'); - -})(); 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 7b3cc75..00ef3ee 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/utils.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/utils.js @@ -308,20 +308,7 @@ return 'WebSocket' in window; }, - /** - * 檢查 localStorage 是否可用 - * @returns {boolean} localStorage 是否可用 - */ - isLocalStorageSupported: function() { - try { - const test = '__localStorage_test__'; - localStorage.setItem(test, test); - localStorage.removeItem(test); - return true; - } catch (e) { - return false; - } - }, + /** * HTML 轉義函數 diff --git a/src/mcp_feedback_enhanced/web/templates/index.html b/src/mcp_feedback_enhanced/web/templates/index.html index b266a18..f66db39 100644 --- a/src/mcp_feedback_enhanced/web/templates/index.html +++ b/src/mcp_feedback_enhanced/web/templates/index.html @@ -319,7 +319,7 @@ - +