feat: persist active session across page navigation with inactivity TTL
Session ID is stored in localStorage keyed to user+persona. On page load it's silently restored if within 30 min of last activity. Timestamp updates on every sent message. New session / delete session clears the stored ID so the TTL logic stays consistent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,31 @@
|
||||
let currentHistory = []; // mirrors backend session [{role, content}, ...]
|
||||
let talkThinkingDiv = null; // pending "thinking…" bubble for live Talk updates
|
||||
|
||||
// ── Session persistence ───────────────────────────────────────
|
||||
// Survives page navigation (help, settings, etc.) within the same browser.
|
||||
// Expires after SESSION_TTL_MS of inactivity.
|
||||
const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
const _sid_key = `cx_sid_${CORTEX_USER}_${CORTEX_PERSONA}`;
|
||||
const _sid_ts_key = `cx_sid_ts_${CORTEX_USER}_${CORTEX_PERSONA}`;
|
||||
|
||||
function persist_session() {
|
||||
if (!sessionId) return;
|
||||
localStorage.setItem(_sid_key, sessionId);
|
||||
localStorage.setItem(_sid_ts_key, String(Date.now()));
|
||||
}
|
||||
|
||||
function clear_stored_session() {
|
||||
localStorage.removeItem(_sid_key);
|
||||
localStorage.removeItem(_sid_ts_key);
|
||||
}
|
||||
|
||||
function get_stored_session() {
|
||||
const id = localStorage.getItem(_sid_key);
|
||||
const ts = parseInt(localStorage.getItem(_sid_ts_key) || '0', 10);
|
||||
if (!id || Date.now() - ts > SESSION_TTL_MS) return null;
|
||||
return id;
|
||||
}
|
||||
|
||||
// ── Enter toggle ─────────────────────────────────────────────
|
||||
// Default: Ctrl+Enter sends. Stored in localStorage.
|
||||
let ctrlEnterMode = localStorage.getItem('ctrlEnterSend') !== 'false';
|
||||
@@ -314,6 +339,7 @@
|
||||
const newItem = makeItem('new', '+ New session', '');
|
||||
newItem.addEventListener('click', () => {
|
||||
sessionId = null;
|
||||
clear_stored_session();
|
||||
currentHistory = [];
|
||||
messagesEl.innerHTML = '';
|
||||
sessionEl.textContent = '';
|
||||
@@ -392,6 +418,7 @@
|
||||
await fetch(`/sessions/${s.session_id}?${_fileParams}`, { method: 'DELETE' });
|
||||
if (sessionId === s.session_id) {
|
||||
sessionId = null;
|
||||
clear_stored_session();
|
||||
currentHistory = [];
|
||||
messagesEl.innerHTML = '';
|
||||
sessionEl.textContent = '';
|
||||
@@ -425,10 +452,11 @@
|
||||
return item;
|
||||
}
|
||||
|
||||
async function resumeSession(id) {
|
||||
async function resumeSession(id, silent = false) {
|
||||
talkThinkingDiv = null;
|
||||
if (id && id.startsWith('nct_')) sessionsBtn.classList.remove('talk-badge');
|
||||
const res = await fetch(`/history/${id}?${_fileParams}`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
|
||||
messagesEl.innerHTML = '';
|
||||
@@ -444,10 +472,11 @@
|
||||
attachHistoryControls(msgDiv, i);
|
||||
}
|
||||
|
||||
addMessage('system', `Resumed session ${id}`);
|
||||
if (!silent) addMessage('system', `Resumed session ${id}`);
|
||||
scrollToBottom();
|
||||
sessionsPanel.classList.remove('open');
|
||||
inputEl.focus();
|
||||
persist_session();
|
||||
}
|
||||
|
||||
function timeAgo(iso) {
|
||||
@@ -793,6 +822,7 @@
|
||||
if (data.type === 'response') {
|
||||
sessionId = data.session_id;
|
||||
sessionEl.textContent = `session: ${sessionId}`;
|
||||
persist_session();
|
||||
thinkingDiv.className = 'message assistant';
|
||||
setMessageText(thinkingDiv, 'assistant', data.response);
|
||||
const assistHistIdx = currentHistory.length;
|
||||
@@ -893,6 +923,7 @@
|
||||
if (job.session_id) {
|
||||
sessionId = job.session_id;
|
||||
sessionEl.textContent = `session: ${sessionId}`;
|
||||
persist_session();
|
||||
}
|
||||
|
||||
const userHistIdx = currentHistory.length - 1; // pushed before fetch
|
||||
@@ -1315,3 +1346,10 @@
|
||||
// and seed the mode UI (which also calls render_icons internally).
|
||||
update_mode_ui();
|
||||
render_icons();
|
||||
|
||||
// ── Auto-restore last session ─────────────────────────────────
|
||||
// Silently resume if within the inactivity TTL; clears stored ID on error.
|
||||
{
|
||||
const stored = get_stored_session();
|
||||
if (stored) resumeSession(stored, true).catch(clear_stored_session);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user