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 @@
-
+