🔨 會話歷史改為本地保存,廢除 localstorage 的方式

This commit is contained in:
Minidoracat 2025-06-15 13:45:38 +08:00
parent e961a2c1c8
commit e28f5a1ebd
3 changed files with 241 additions and 61 deletions

View File

@ -306,6 +306,91 @@ def setup_routes(manager: "WebUIManager"):
content={"status": "error", "message": f"清除失敗: {e!s}"},
)
@manager.app.get("/api/load-session-history")
async def load_session_history():
"""從檔案載入會話歷史"""
try:
# 使用統一的設定檔案路徑
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
history_file = config_dir / "session_history.json"
if history_file.exists():
with open(history_file, encoding="utf-8") as f:
history_data = json.load(f)
debug_log(f"會話歷史已從檔案載入: {history_file}")
# 確保資料格式相容性
if isinstance(history_data, dict):
# 新格式:包含版本資訊和其他元資料
sessions = history_data.get("sessions", [])
last_cleanup = history_data.get("lastCleanup", 0)
else:
# 舊格式:直接是會話陣列(向後相容)
sessions = history_data if isinstance(history_data, list) else []
last_cleanup = 0
# 回傳與 localStorage 格式相容的資料
return JSONResponse(
content={"sessions": sessions, "lastCleanup": last_cleanup}
)
debug_log("會話歷史檔案不存在,返回空歷史")
return JSONResponse(content={"sessions": [], "lastCleanup": 0})
except Exception as e:
debug_log(f"載入會話歷史失敗: {e}")
return JSONResponse(
status_code=500,
content={"status": "error", "message": f"載入失敗: {e!s}"},
)
@manager.app.post("/api/save-session-history")
async def save_session_history(request: Request):
"""保存會話歷史到檔案"""
try:
data = await request.json()
# 使用統一的設定檔案路徑
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
config_dir.mkdir(parents=True, exist_ok=True)
history_file = config_dir / "session_history.json"
# 建立新格式的資料結構
history_data = {
"version": "1.0",
"sessions": data.get("sessions", []),
"lastCleanup": data.get("lastCleanup", 0),
"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)
debug_log(f"會話歷史已保存到: {history_file}")
session_count = len(history_data["sessions"])
debug_log(f"保存了 {session_count} 個會話記錄")
return JSONResponse(
content={
"status": "success",
"message": f"會話歷史已保存({session_count} 個會話)",
}
)
except Exception as e:
debug_log(f"保存會話歷史失敗: {e}")
return JSONResponse(
status_code=500,
content={"status": "error", "message": f"保存失敗: {e!s}"},
)
@manager.app.get("/api/active-tabs")
async def get_active_tabs():
"""獲取活躍標籤頁信息 - 優先使用全局狀態"""

View File

@ -74,6 +74,9 @@
},
onStatsChange: function(stats) {
self.handleStatsChange(stats);
},
onDataChanged: function() {
self.handleDataChanged();
}
});
};
@ -115,6 +118,22 @@
this.uiRenderer.renderStats(stats);
};
/**
* 處理資料變更用於異步載入完成後的更新
*/
SessionManager.prototype.handleDataChanged = function() {
console.log('📋 處理資料變更,重新渲染所有內容');
// 重新渲染所有內容
const currentSession = this.dataManager.getCurrentSession();
const history = this.dataManager.getSessionHistory();
const stats = this.dataManager.getStats();
this.uiRenderer.renderCurrentSession(currentSession);
this.uiRenderer.renderSessionHistory(history);
this.uiRenderer.renderStats(stats);
};
/**
* 設置事件監聽器
*/

View File

@ -32,19 +32,18 @@
averageDuration: 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.onDataChanged = options.onDataChanged || null;
// 初始化:載入歷史記錄並清理過期資料
this.loadFromLocalStorage();
this.cleanupExpiredSessions();
this.updateStats();
// 注意loadFromServer 是異步的,會在載入完成後自動觸發更新
this.loadFromServer();
console.log('📊 SessionDataManager 初始化完成');
}
@ -227,8 +226,8 @@
this.sessionHistory = this.sessionHistory.slice(0, 10);
}
// 保存到 localStorage
this.saveToLocalStorage();
// 保存到伺服器端
this.saveToServer();
this.updateStats();
@ -390,8 +389,8 @@
}
});
// 保存到 localStorage
this.saveToLocalStorage();
// 保存到伺服器端
this.saveToServer();
console.log('📊 所有用戶訊息記錄已清空');
return true;
@ -410,7 +409,7 @@
if (session && session.user_messages) {
session.user_messages = [];
this.saveToLocalStorage();
this.saveToServer();
console.log('📊 會話用戶訊息記錄已清空:', sessionId);
return true;
}
@ -458,9 +457,17 @@
this.sessionStats.todayCount = todaySessions.length;
// 計算今日平均持續時間
const todayCompletedSessions = todaySessions.filter(s => s.duration && s.duration > 0);
const todayCompletedSessions = todaySessions.filter(function(s) {
// 過濾有效的持續時間:大於 0 且小於 24 小時86400 秒)
return s.duration && s.duration > 0 && s.duration < 86400;
});
if (todayCompletedSessions.length > 0) {
const totalDuration = todayCompletedSessions.reduce((sum, s) => sum + s.duration, 0);
const totalDuration = todayCompletedSessions.reduce(function(sum, s) {
// 確保持續時間是合理的數值
const duration = Math.min(s.duration, 86400); // 最大 24 小時
return sum + duration;
}, 0);
this.sessionStats.averageDuration = Math.round(totalDuration / todayCompletedSessions.length);
} else {
this.sessionStats.averageDuration = 0;
@ -495,8 +502,8 @@
SessionDataManager.prototype.clearHistory = function() {
this.sessionHistory = [];
// 清空 localStorage
this.clearLocalStorage();
// 清空伺服器端資料
this.clearServerData();
this.updateStats();
if (this.onHistoryChange) {
@ -562,65 +569,134 @@
};
/**
* localStorage 載入會話歷史
* 伺服器載入會話歷史
*/
SessionDataManager.prototype.loadFromLocalStorage = function() {
if (!window.localStorage) {
console.warn('📊 localStorage 不可用');
return;
SessionDataManager.prototype.loadFromServer = function() {
const self = this;
fetch('/api/load-session-history')
.then(function(response) {
if (response.ok) {
return response.json();
} else {
throw new Error('伺服器回應錯誤: ' + response.status);
}
})
.then(function(data) {
if (data && Array.isArray(data.sessions)) {
self.sessionHistory = data.sessions;
console.log('📊 從伺服器載入', self.sessionHistory.length, '個會話');
// 載入完成後進行清理和統計更新
self.cleanupExpiredSessions();
self.updateStats();
// 觸發歷史記錄變更回調
if (self.onHistoryChange) {
self.onHistoryChange(self.sessionHistory);
}
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, '個會話');
// 觸發資料變更回調
if (self.onDataChanged) {
self.onDataChanged();
}
} else {
console.warn('📊 伺服器回應格式錯誤:', data);
self.sessionHistory = [];
// 即使沒有資料也要更新統計
self.updateStats();
// 觸發歷史記錄變更回調(空列表)
if (self.onHistoryChange) {
self.onHistoryChange(self.sessionHistory);
}
if (self.onDataChanged) {
self.onDataChanged();
}
}
} catch (error) {
console.error('📊 從 localStorage 載入會話歷史失敗:', error);
})
.catch(function(error) {
console.warn('📊 從伺服器載入會話歷史失敗:', error);
self.sessionHistory = [];
// 載入失敗時也要更新統計
self.updateStats();
// 觸發歷史記錄變更回調(空列表)
if (self.onHistoryChange) {
self.onHistoryChange(self.sessionHistory);
}
if (self.onDataChanged) {
self.onDataChanged();
}
});
};
/**
* 保存會話歷史到 localStorage
* 保存會話歷史到伺服器
*/
SessionDataManager.prototype.saveToLocalStorage = function() {
if (!window.localStorage) {
console.warn('📊 localStorage 不可用');
return;
}
try {
SessionDataManager.prototype.saveToServer = function() {
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);
fetch('/api/save-session-history', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(function(response) {
if (response.ok) {
console.log('📊 已保存', data.sessions.length, '個會話到伺服器');
return response.json();
} else {
throw new Error('伺服器回應錯誤: ' + response.status);
}
})
.then(function(result) {
console.log('📊 伺服器保存回應:', result.message);
})
.catch(function(error) {
console.error('📊 保存會話歷史到伺服器失敗:', 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.clearServerData = function() {
const emptyData = {
sessions: [],
lastCleanup: TimeUtils.getCurrentTimestamp()
};
fetch('/api/save-session-history', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(emptyData)
})
.then(function(response) {
if (response.ok) {
console.log('📊 已清空伺服器端的會話歷史');
} else {
throw new Error('伺服器回應錯誤: ' + response.status);
}
})
.catch(function(error) {
console.error('📊 清空伺服器端會話歷史失敗:', error);
});
};
/**
* 清理過期的會話
*/
@ -642,7 +718,7 @@
const cleanedCount = originalCount - this.sessionHistory.length;
if (cleanedCount > 0) {
console.log('📊 清理了', cleanedCount, '個過期會話');
this.saveToLocalStorage();
this.saveToServer();
}
};