mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +08:00
🔨 重構了保存機制,移除 localStorage 相關
This commit is contained in:
parent
81c6177b02
commit
be573a9a95
@ -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
|
||||
@manager.app.post("/api/log-level")
|
||||
async def set_log_level(request: Request):
|
||||
"""設定日誌等級"""
|
||||
try:
|
||||
data = await request.json()
|
||||
log_level = data.get("logLevel")
|
||||
|
||||
# 更新會話的活躍標籤頁
|
||||
current_session.active_tabs = valid_global_tabs.copy()
|
||||
manager.global_active_tabs = valid_global_tabs
|
||||
if not log_level or log_level not in ["DEBUG", "INFO", "WARN", "ERROR"]:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={
|
||||
"error": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一"
|
||||
},
|
||||
)
|
||||
|
||||
# 使用統一的設定檔案路徑
|
||||
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
settings_file = config_dir / "ui_settings.json"
|
||||
|
||||
# 載入現有設定或創建新設定
|
||||
settings_data = {}
|
||||
if settings_file.exists():
|
||||
with open(settings_file, encoding="utf-8") as f:
|
||||
settings_data = json.load(f)
|
||||
|
||||
# 更新日誌等級
|
||||
settings_data["logLevel"] = log_level
|
||||
|
||||
# 保存設定到檔案
|
||||
with open(settings_file, "w", encoding="utf-8") as f:
|
||||
json.dump(settings_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
debug_log(f"日誌等級已設定為: {log_level}")
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"has_session": current_session is not None,
|
||||
"active_tabs": valid_global_tabs,
|
||||
"count": len(valid_global_tabs),
|
||||
"status": "success",
|
||||
"logLevel": log_level,
|
||||
"message": "日誌等級已更新",
|
||||
}
|
||||
)
|
||||
|
||||
@manager.app.post("/api/register-tab")
|
||||
async def register_tab(request: Request):
|
||||
"""註冊新標籤頁"""
|
||||
try:
|
||||
data = await request.json()
|
||||
tab_id = data.get("tabId")
|
||||
|
||||
if not tab_id:
|
||||
return JSONResponse(status_code=400, content={"error": "缺少 tabId"})
|
||||
|
||||
current_session = manager.get_current_session()
|
||||
if not current_session:
|
||||
return JSONResponse(status_code=404, content={"error": "沒有活躍會話"})
|
||||
|
||||
# 註冊標籤頁
|
||||
tab_info = {
|
||||
"timestamp": time.time() * 1000, # 毫秒時間戳
|
||||
"last_seen": time.time(),
|
||||
"registered_at": time.time(),
|
||||
}
|
||||
|
||||
if not hasattr(current_session, "active_tabs"):
|
||||
current_session.active_tabs = {}
|
||||
|
||||
current_session.active_tabs[tab_id] = tab_info
|
||||
|
||||
# 同時更新全局標籤頁狀態
|
||||
manager.global_active_tabs[tab_id] = tab_info
|
||||
|
||||
debug_log(f"標籤頁已註冊: {tab_id}")
|
||||
|
||||
return JSONResponse(
|
||||
content={"status": "success", "tabId": tab_id, "registered": True}
|
||||
)
|
||||
|
||||
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:
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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}`);
|
||||
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 = () => {
|
||||
if (window.feedbackApp && window.feedbackApp.settingsManager) {
|
||||
window.feedbackApp.settingsManager.set('language', lang);
|
||||
} else {
|
||||
this.setLanguage(lang);
|
||||
}
|
||||
};
|
||||
option.addEventListener('click', option._languageClickHandler);
|
||||
});
|
||||
|
@ -312,12 +312,6 @@
|
||||
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;
|
||||
@ -326,14 +320,78 @@
|
||||
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()]);
|
||||
|
||||
})();
|
||||
|
@ -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 建構函數初始化完成 - 即時保存模式');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,27 +70,15 @@
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 合併設定
|
||||
@ -287,18 +202,10 @@
|
||||
// 特殊處理語言變更
|
||||
if (key === 'language' && oldValue !== value) {
|
||||
this.handleLanguageChange(value);
|
||||
// 語言變更是重要操作,立即保存
|
||||
this.saveToLocalStorage();
|
||||
this.saveToServerImmediate();
|
||||
}
|
||||
|
||||
// 觸發回調
|
||||
if (this.onSettingsChange) {
|
||||
this.onSettingsChange(this.currentSettings);
|
||||
}
|
||||
} else {
|
||||
// 一般設定變更使用防抖保存
|
||||
// 所有設定變更都即時保存
|
||||
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) {
|
||||
|
@ -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 模組載入完成');
|
||||
|
||||
})();
|
@ -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 轉義函數
|
||||
|
@ -319,7 +319,7 @@
|
||||
<script src="/static/js/i18n.js?v=2025010505"></script>
|
||||
<!-- 載入所有模組 -->
|
||||
<script src="/static/js/modules/utils.js?v=2025010505"></script>
|
||||
<script src="/static/js/modules/tab-manager.js?v=2025010505"></script>
|
||||
|
||||
<script src="/static/js/modules/websocket-manager.js?v=2025010505"></script>
|
||||
<script src="/static/js/modules/image-handler.js?v=2025010505"></script>
|
||||
<script src="/static/js/modules/settings-manager.js?v=2025010505"></script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user