|
|
|
|
|
|
|
|
|
|
|
import { Logger } from '../core/logger.js'; |
|
|
import { appState } from '../core/state.js'; |
|
|
import { storageManager } from '../core/storage.js'; |
|
|
import { domManager } from './dom-manager.js'; |
|
|
import { messageManager } from './message-manager.js'; |
|
|
import { imageHandler } from './image-handler.js'; |
|
|
|
|
|
export class SessionManager { |
|
|
constructor() { |
|
|
this.isInitialized = false; |
|
|
this.periodicSaveInterval = null; |
|
|
} |
|
|
|
|
|
initialize() { |
|
|
if (this.isInitialized) return; |
|
|
|
|
|
|
|
|
this.cleanupGhostSessions(); |
|
|
|
|
|
this.setupEventListeners(); |
|
|
this.refreshSessionsList(); |
|
|
this.isInitialized = true; |
|
|
|
|
|
Logger.debug('Session', 'Session manager initialized'); |
|
|
} |
|
|
|
|
|
setupEventListeners() { |
|
|
|
|
|
domManager.getElement('newSessionBtn')?.addEventListener('click', () => this.startNewSession()); |
|
|
domManager.getElement('sessionsToggle')?.addEventListener('click', () => this.toggleSessions()); |
|
|
domManager.getElement('clearSessionsBtn')?.addEventListener('click', () => this.clearAllSessionsEnhanced()); |
|
|
domManager.getElement('exportSessionsBtn')?.addEventListener('click', () => this.exportSessions()); |
|
|
domManager.getElement('importSessionsBtn')?.addEventListener('click', () => this.triggerImportSessions()); |
|
|
|
|
|
|
|
|
domManager.getElement('importSessionsInput')?.addEventListener('change', (e) => this.handleImportFile(e)); |
|
|
|
|
|
|
|
|
document.querySelector('.sessions-header')?.addEventListener('click', () => { |
|
|
document.getElementById('sessionsToggle')?.click(); |
|
|
}); |
|
|
|
|
|
Logger.debug('Session', 'Event listeners set up'); |
|
|
} |
|
|
|
|
|
startNewSession() { |
|
|
Logger.debug('Session', 'Start New Session button clicked'); |
|
|
this.resetToNewSessionState(); |
|
|
domManager.updateStatus('Ready to start a new session', 'success'); |
|
|
} |
|
|
|
|
|
resetToNewSessionState() { |
|
|
console.log('[DEBUG] Resetting to new session state'); |
|
|
|
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
console.log('[DEBUG] Saving current session before reset'); |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
|
|
|
appState.currentSessionData.problemText = domManager.getElement('questionInput')?.value.trim() || ''; |
|
|
const imageElement = domManager.getElement('imagePreview'); |
|
|
appState.currentSessionData.image = imageElement?.style.display !== 'none' ? imageElement.src : null; |
|
|
appState.currentSessionData.title = this.generateSessionTitle(appState.currentSessionData.problemText); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
} |
|
|
|
|
|
|
|
|
appState.selectedSessionId = null; |
|
|
appState.currentSessionData = null; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.session-item').forEach(item => { |
|
|
item.classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
|
|
|
this.clearAndEnableInputs(); |
|
|
|
|
|
|
|
|
messageManager.clearChatAndRestoreWelcome(); |
|
|
|
|
|
|
|
|
if (window.interactiveFeedback) { |
|
|
window.interactiveFeedback.removeFeedbackPanel(); |
|
|
window.interactiveFeedback.removeRestoreButton(); |
|
|
} |
|
|
|
|
|
|
|
|
document.querySelectorAll('.final-artifacts-compact').forEach(panel => { |
|
|
panel.remove(); |
|
|
}); |
|
|
|
|
|
|
|
|
import('./settings-manager.js').then(({ settingsManager }) => { |
|
|
settingsManager.clearPerSessionRules(); |
|
|
}); |
|
|
|
|
|
this.updateCurrentSessionDisplay(); |
|
|
console.log('[DEBUG] Reset to new session state completed'); |
|
|
} |
|
|
|
|
|
clearAndEnableInputs() { |
|
|
|
|
|
domManager.clearInputs(); |
|
|
|
|
|
|
|
|
const questionInputElement = domManager.getElement('questionInput'); |
|
|
const solveBtnElement = domManager.getElement('solveBtn'); |
|
|
|
|
|
if (questionInputElement) { |
|
|
questionInputElement.disabled = false; |
|
|
questionInputElement.style.backgroundColor = ''; |
|
|
questionInputElement.style.cursor = ''; |
|
|
questionInputElement.title = ''; |
|
|
questionInputElement.placeholder = "Enter your problem here... (e.g., 'What is the square root of 144?', 'Solve this math puzzle', etc.)"; |
|
|
} |
|
|
|
|
|
if (solveBtnElement && !appState.isSolving) { |
|
|
solveBtnElement.style.display = 'inline-flex'; |
|
|
solveBtnElement.disabled = false; |
|
|
solveBtnElement.title = ''; |
|
|
} |
|
|
|
|
|
|
|
|
this.removeReadOnlyMessage(); |
|
|
|
|
|
|
|
|
if (typeof feather !== 'undefined') { |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
|
|
|
setInputsReadOnly(reason = 'This session has been used and is now read-only') { |
|
|
const questionInputElement = domManager.getElement('questionInput'); |
|
|
const solveBtnElement = domManager.getElement('solveBtn'); |
|
|
|
|
|
if (questionInputElement) { |
|
|
questionInputElement.disabled = true; |
|
|
questionInputElement.style.backgroundColor = 'var(--gray-100)'; |
|
|
questionInputElement.style.cursor = 'not-allowed'; |
|
|
questionInputElement.title = reason; |
|
|
questionInputElement.placeholder = 'This session is read-only. Start a new session to solve another problem.'; |
|
|
} |
|
|
|
|
|
if (solveBtnElement) { |
|
|
solveBtnElement.style.display = 'none'; |
|
|
solveBtnElement.disabled = true; |
|
|
} |
|
|
|
|
|
|
|
|
this.showReadOnlyMessage(); |
|
|
} |
|
|
|
|
|
showReadOnlyMessage() { |
|
|
|
|
|
this.removeReadOnlyMessage(); |
|
|
|
|
|
const messageEl = document.createElement('div'); |
|
|
messageEl.className = 'session-readonly-message'; |
|
|
messageEl.style.cssText = ` |
|
|
background: var(--warning-50); |
|
|
border: 1px solid var(--warning-200); |
|
|
border-radius: 8px; |
|
|
padding: 12px; |
|
|
margin-top: 8px; |
|
|
font-size: 13px; |
|
|
color: var(--warning-700); |
|
|
text-align: center; |
|
|
`; |
|
|
messageEl.innerHTML = ` |
|
|
<i data-feather="info" style="width: 14px; height: 14px; margin-right: 6px;"></i> |
|
|
This session is read-only. Click "Start New Session" to solve a new problem. |
|
|
`; |
|
|
|
|
|
|
|
|
const buttonGroup = document.querySelector('.button-group'); |
|
|
if (buttonGroup) { |
|
|
buttonGroup.insertAdjacentElement('afterend', messageEl); |
|
|
|
|
|
if (typeof feather !== 'undefined') { |
|
|
feather.replace(messageEl); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
removeReadOnlyMessage() { |
|
|
const message = document.querySelector('.session-readonly-message'); |
|
|
if (message) { |
|
|
message.remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
isSessionUsed(session) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const readOnlyStatuses = ['completed', 'interrupted']; |
|
|
return readOnlyStatuses.includes(session?.status); |
|
|
} |
|
|
|
|
|
toggleSessions() { |
|
|
appState.sessionsExpanded = !appState.sessionsExpanded; |
|
|
|
|
|
const sessionsContainer = domManager.getElement('sessionsContainer'); |
|
|
const sessionsToggle = domManager.getElement('sessionsToggle'); |
|
|
|
|
|
if (appState.sessionsExpanded) { |
|
|
sessionsContainer?.classList.add('expanded'); |
|
|
sessionsToggle?.classList.add('expanded'); |
|
|
} else { |
|
|
sessionsContainer?.classList.remove('expanded'); |
|
|
sessionsToggle?.classList.remove('expanded'); |
|
|
} |
|
|
|
|
|
Logger.debug('Session', `Sessions panel ${appState.sessionsExpanded ? 'expanded' : 'collapsed'}`); |
|
|
} |
|
|
|
|
|
clearAllSessions() { |
|
|
if (confirm('Are you sure you want to clear all session history? This cannot be undone.')) { |
|
|
try { |
|
|
storageManager.clearAllSessions(); |
|
|
this.refreshSessionsList(); |
|
|
domManager.updateStatus('All sessions cleared', 'success'); |
|
|
Logger.debug('Session', 'All sessions cleared by user'); |
|
|
} catch (error) { |
|
|
Logger.error('Session', 'Error clearing sessions:', error); |
|
|
domManager.updateStatus('Error clearing sessions', 'error'); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
exportSessions() { |
|
|
try { |
|
|
const result = storageManager.exportSessions(); |
|
|
if (result) { |
|
|
const sessions = storageManager.loadSessions(); |
|
|
const defaultSessionIds = storageManager.getDefaultSessionIds(); |
|
|
const userSessionCount = Object.keys(sessions).length - defaultSessionIds.length; |
|
|
|
|
|
if (userSessionCount > 0) { |
|
|
domManager.updateStatus(`Exported ${userSessionCount} user session(s) successfully`, 'success'); |
|
|
} else { |
|
|
domManager.updateStatus('No user sessions to export (default sessions are excluded)', 'info'); |
|
|
} |
|
|
Logger.debug('Session', `Sessions exported by user: ${userSessionCount} user sessions`); |
|
|
} else { |
|
|
domManager.updateStatus('Error exporting sessions', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
Logger.error('Session', 'Error exporting sessions:', error); |
|
|
domManager.updateStatus('Error exporting sessions', 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
triggerImportSessions() { |
|
|
const fileInput = domManager.getElement('importSessionsInput'); |
|
|
if (fileInput) { |
|
|
fileInput.click(); |
|
|
} |
|
|
} |
|
|
|
|
|
async handleImportFile(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (!file) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (!file.name.endsWith('.json')) { |
|
|
domManager.updateStatus('Please select a JSON file', 'warning'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
domManager.updateStatus('Importing sessions...', 'info'); |
|
|
|
|
|
|
|
|
const fileContent = await this.readFileAsText(file); |
|
|
|
|
|
|
|
|
const result = await storageManager.importSessions(fileContent, { |
|
|
merge: true, |
|
|
overwriteDuplicates: false |
|
|
}); |
|
|
|
|
|
|
|
|
if (result.imported > 0) { |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
let message = `Successfully imported ${result.imported} session(s)`; |
|
|
if (result.skipped > 0) { |
|
|
message += ` (${result.skipped} skipped due to duplicates)`; |
|
|
} |
|
|
|
|
|
domManager.updateStatus(message, 'success'); |
|
|
Logger.debug('Session', `Import completed: ${result.imported} imported, ${result.skipped} skipped`); |
|
|
|
|
|
|
|
|
if (result.duplicates > 0) { |
|
|
const shouldOverwrite = confirm( |
|
|
`Found ${result.duplicates} duplicate session(s). ` + |
|
|
`Would you like to overwrite them with the imported versions?` |
|
|
); |
|
|
|
|
|
if (shouldOverwrite) { |
|
|
const overwriteResult = await storageManager.importSessions(fileContent, { |
|
|
merge: true, |
|
|
overwriteDuplicates: true |
|
|
}); |
|
|
|
|
|
this.refreshSessionsList(); |
|
|
domManager.updateStatus( |
|
|
`Import completed: ${overwriteResult.imported} sessions imported (including overwrites)`, |
|
|
'success' |
|
|
); |
|
|
} |
|
|
} |
|
|
} else if (result.skipped > 0) { |
|
|
domManager.updateStatus('No new sessions imported - all sessions already exist', 'warning'); |
|
|
} else { |
|
|
domManager.updateStatus('No valid sessions found in file', 'warning'); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
Logger.error('Session', 'Error importing sessions:', error); |
|
|
|
|
|
let errorMessage = 'Error importing sessions'; |
|
|
if (error.message.includes('Invalid import data')) { |
|
|
errorMessage = 'Invalid file format - please select a valid PIPS session export file'; |
|
|
} else if (error.message.includes('JSON')) { |
|
|
errorMessage = 'Invalid JSON file format'; |
|
|
} |
|
|
|
|
|
domManager.updateStatus(errorMessage, 'error'); |
|
|
} finally { |
|
|
|
|
|
event.target.value = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
readFileAsText(file) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => resolve(e.target.result); |
|
|
reader.onerror = (e) => reject(new Error('Failed to read file')); |
|
|
reader.readAsText(file); |
|
|
}); |
|
|
} |
|
|
|
|
|
downloadSingleSession(sessionId) { |
|
|
try { |
|
|
const success = storageManager.exportSingleSession(sessionId); |
|
|
if (success) { |
|
|
domManager.updateStatus('Session downloaded successfully', 'success'); |
|
|
Logger.debug('Session', `Single session ${sessionId} exported by user`); |
|
|
} else { |
|
|
domManager.updateStatus('Error: Session not found', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
Logger.error('Session', 'Error downloading session:', error); |
|
|
domManager.updateStatus('Error downloading session', 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
saveCurrentSessionToStorage() { |
|
|
if (!appState.currentSessionData) { |
|
|
console.log('[DEBUG] No current session data to save'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const problemText = domManager.getElement('questionInput')?.value.trim() || ''; |
|
|
const imageElement = domManager.getElement('imagePreview'); |
|
|
const image = imageElement?.style.display !== 'none' ? imageElement.src : null; |
|
|
|
|
|
|
|
|
appState.currentSessionData.problemText = problemText; |
|
|
appState.currentSessionData.image = image; |
|
|
appState.currentSessionData.title = this.generateSessionTitle(problemText); |
|
|
|
|
|
|
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
|
|
|
|
|
|
const chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.chatHistory = chatHistory; |
|
|
|
|
|
console.log(`[DEBUG] Saving session ${appState.currentSessionData.id}:`); |
|
|
console.log(`[DEBUG] - Title: ${appState.currentSessionData.title}`); |
|
|
console.log(`[DEBUG] - Problem text length: ${problemText.length}`); |
|
|
console.log(`[DEBUG] - Chat history messages: ${chatHistory.length}`); |
|
|
if (chatHistory.length > 0) { |
|
|
console.log(`[DEBUG] - Sample message: ${chatHistory[0].sender} - ${chatHistory[0].content.substring(0, 50)}...`); |
|
|
} |
|
|
|
|
|
|
|
|
storageManager.saveSession(appState.currentSessionData.id, appState.currentSessionData); |
|
|
|
|
|
console.log(`[DEBUG] Successfully saved session: ${appState.currentSessionData.id} with ${appState.currentSessionData.chatHistory.length} messages`); |
|
|
} |
|
|
|
|
|
generateSessionTitle(problemText) { |
|
|
if (!problemText || problemText.trim() === '') { |
|
|
return 'Untitled Session'; |
|
|
} |
|
|
|
|
|
|
|
|
const cleaned = problemText.trim().replace(/\s+/g, ' '); |
|
|
const maxLength = 50; |
|
|
|
|
|
if (cleaned.length <= maxLength) { |
|
|
return cleaned; |
|
|
} |
|
|
|
|
|
|
|
|
const truncated = cleaned.substring(0, maxLength); |
|
|
const lastSpace = truncated.lastIndexOf(' '); |
|
|
|
|
|
if (lastSpace > maxLength * 0.6) { |
|
|
return truncated.substring(0, lastSpace) + '...'; |
|
|
} |
|
|
|
|
|
return truncated + '...'; |
|
|
} |
|
|
|
|
|
createNewSession(problemText, image = null) { |
|
|
const sessionId = this.generateSessionId(); |
|
|
const now = new Date().toISOString(); |
|
|
|
|
|
|
|
|
const hasContent = problemText && problemText.trim().length > 0; |
|
|
const title = hasContent ? this.generateSessionTitle(problemText) : 'Untitled Session'; |
|
|
|
|
|
const newSession = { |
|
|
id: sessionId, |
|
|
title: title, |
|
|
problemText: problemText || '', |
|
|
image: image, |
|
|
createdAt: now, |
|
|
lastUsed: now, |
|
|
status: 'active', |
|
|
chatHistory: [] |
|
|
}; |
|
|
|
|
|
console.log(`[DEBUG] Created new session: ${sessionId}, title: "${title}", hasContent: ${hasContent}`); |
|
|
return newSession; |
|
|
} |
|
|
|
|
|
generateSessionId() { |
|
|
return 'session_' + Math.random().toString(36).substr(2, 16) + '_' + Date.now(); |
|
|
} |
|
|
|
|
|
switchToSession(sessionId) { |
|
|
console.log(`[DEBUG] Switching to session: ${sessionId}`); |
|
|
|
|
|
|
|
|
if (appState.isSolving) { |
|
|
domManager.updateStatus('Cannot switch sessions while solving. Please stop the current task first.', 'warning'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (window.sessionSwitchInProgress) { |
|
|
console.log('[DEBUG] Session switch already in progress, ignoring'); |
|
|
return; |
|
|
} |
|
|
window.sessionSwitchInProgress = true; |
|
|
|
|
|
try { |
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
console.log('[DEBUG] Saving current session state before switching'); |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
|
|
|
appState.currentSessionData.problemText = domManager.getElement('questionInput')?.value.trim() || ''; |
|
|
const imageElement = domManager.getElement('imagePreview'); |
|
|
appState.currentSessionData.image = imageElement?.style.display !== 'none' ? imageElement.src : null; |
|
|
appState.currentSessionData.title = this.generateSessionTitle(appState.currentSessionData.problemText); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
} |
|
|
|
|
|
|
|
|
let sessions = storageManager.loadSessions(); |
|
|
console.log(`[DEBUG] Loaded sessions from storage:`, Object.keys(sessions)); |
|
|
|
|
|
|
|
|
const allSessions = { ...sessions }; |
|
|
if (appState.currentSessionData && appState.currentSessionData.id) { |
|
|
allSessions[appState.currentSessionData.id] = appState.currentSessionData; |
|
|
console.log(`[DEBUG] Added current session to combined sessions: ${appState.currentSessionData.id}`); |
|
|
} |
|
|
|
|
|
console.log(`[DEBUG] All available sessions:`, Object.keys(allSessions)); |
|
|
|
|
|
|
|
|
Object.entries(allSessions).forEach(([id, sess]) => { |
|
|
console.log(`[DEBUG] Session ${id}: title="${sess.title}", status="${sess.status}"`); |
|
|
}); |
|
|
|
|
|
let session = allSessions[sessionId]; |
|
|
|
|
|
if (!session) { |
|
|
console.error(`[DEBUG] Session not found: ${sessionId}`); |
|
|
console.error(`[DEBUG] Available sessions:`, Object.keys(allSessions)); |
|
|
console.error(`[DEBUG] Current session in state:`, appState.currentSessionData?.id); |
|
|
domManager.updateStatus('Session not found', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
console.log(`[DEBUG] Found session: ${sessionId}, status: ${session.status}, title: ${session.title}`); |
|
|
|
|
|
console.log(`[DEBUG] Loading session: ${sessionId} with ${session.chatHistory ? session.chatHistory.length : 0} messages`); |
|
|
|
|
|
|
|
|
appState.selectedSessionId = sessionId; |
|
|
appState.currentSessionData = { ...session }; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.session-item').forEach(item => { |
|
|
item.classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
|
|
|
const targetElement = document.querySelector(`[data-session-id="${sessionId}"]`); |
|
|
if (targetElement) { |
|
|
console.log(`[DEBUG] Setting selected class on session: ${sessionId}`); |
|
|
targetElement.classList.add('selected'); |
|
|
} else { |
|
|
console.error(`[DEBUG] Target element not found for session: ${sessionId}`); |
|
|
|
|
|
setTimeout(() => { |
|
|
const retryElement = document.querySelector(`[data-session-id="${sessionId}"]`); |
|
|
if (retryElement) { |
|
|
retryElement.classList.add('selected'); |
|
|
console.log(`[DEBUG] Successfully set selected class on retry`); |
|
|
} |
|
|
}, 50); |
|
|
} |
|
|
|
|
|
|
|
|
const questionInput = domManager.getElement('questionInput'); |
|
|
if (questionInput) { |
|
|
questionInput.value = session.problemText || ''; |
|
|
} |
|
|
|
|
|
|
|
|
const isUsedSession = this.isSessionUsed(session); |
|
|
|
|
|
if (isUsedSession) { |
|
|
|
|
|
this.setInputsReadOnly(`This session is ${session.status || 'used'}. Start a new session to solve another problem.`); |
|
|
domManager.updateStatus(`Viewing ${session.status || 'used'} session (read-only)`, 'info'); |
|
|
console.log(`[DEBUG] Session ${sessionId} is read-only (status: ${session.status})`); |
|
|
} else { |
|
|
|
|
|
this.clearAndEnableInputs(); |
|
|
console.log(`[DEBUG] Session ${sessionId} is editable (status: ${session.status})`); |
|
|
} |
|
|
|
|
|
|
|
|
imageHandler.loadSessionImage(session.image); |
|
|
|
|
|
|
|
|
messageManager.loadChatHistory(session.chatHistory || []); |
|
|
|
|
|
domManager.updateStatus(`Switched to session: ${session.title}`, 'success'); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[DEBUG] Error in switchToSession:', error); |
|
|
domManager.updateStatus('Error switching to session', 'error'); |
|
|
} finally { |
|
|
|
|
|
setTimeout(() => { |
|
|
window.sessionSwitchInProgress = false; |
|
|
}, 100); |
|
|
} |
|
|
} |
|
|
|
|
|
deleteSession(sessionId, event) { |
|
|
if (event) { |
|
|
event.stopPropagation(); |
|
|
} |
|
|
|
|
|
console.log(`[DEBUG] Attempting to delete session: ${sessionId}`); |
|
|
|
|
|
if (confirm('Are you sure you want to delete this session?')) { |
|
|
try { |
|
|
|
|
|
const sessions = storageManager.loadSessions(); |
|
|
console.log(`[DEBUG] Loaded ${Object.keys(sessions).length} sessions from storage`); |
|
|
|
|
|
|
|
|
const sessionExistsInStorage = sessions.hasOwnProperty(sessionId); |
|
|
if (sessionExistsInStorage) { |
|
|
delete sessions[sessionId]; |
|
|
storageManager.saveSessions(sessions); |
|
|
console.log(`[DEBUG] Deleted session ${sessionId} from storage`); |
|
|
} else { |
|
|
console.log(`[DEBUG] Session ${sessionId} not found in storage`); |
|
|
} |
|
|
|
|
|
|
|
|
if (appState.currentSessionData && appState.currentSessionData.id === sessionId) { |
|
|
console.log(`[DEBUG] Deleting current session from memory: ${sessionId}`); |
|
|
appState.currentSessionData = null; |
|
|
appState.selectedSessionId = null; |
|
|
|
|
|
|
|
|
domManager.clearInputs(); |
|
|
imageHandler.clearImage(); |
|
|
messageManager.clearChatAndRestoreWelcome(); |
|
|
this.clearAndEnableInputs(); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.final-artifacts-compact').forEach(panel => { |
|
|
panel.remove(); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (appState.selectedSessionId === sessionId) { |
|
|
console.log(`[DEBUG] Clearing selected session: ${sessionId}`); |
|
|
appState.selectedSessionId = null; |
|
|
} |
|
|
|
|
|
|
|
|
const sessionElement = document.querySelector(`[data-session-id="${sessionId}"]`); |
|
|
if (sessionElement) { |
|
|
sessionElement.remove(); |
|
|
console.log(`[DEBUG] Removed DOM element for session: ${sessionId}`); |
|
|
} |
|
|
|
|
|
|
|
|
this.refreshSessionsList(); |
|
|
|
|
|
domManager.updateStatus('Session deleted successfully', 'success'); |
|
|
console.log(`[DEBUG] Session deletion completed: ${sessionId}`); |
|
|
|
|
|
} catch (error) { |
|
|
console.error(`[DEBUG] Error deleting session ${sessionId}:`, error); |
|
|
domManager.updateStatus('Error deleting session', 'error'); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
refreshSessionsList() { |
|
|
console.log('[DEBUG] Updating sessions list'); |
|
|
|
|
|
const sessionsList = domManager.getElement('sessionsList'); |
|
|
|
|
|
if (!sessionsList) { |
|
|
console.error('[DEBUG] Sessions list element not found'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
if (appState.currentSessionData && appState.currentSessionData.id) { |
|
|
console.log('[DEBUG] Ensuring current session is saved before refresh'); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
} |
|
|
|
|
|
const storedSessions = storageManager.loadSessions(); |
|
|
console.log(`[DEBUG] Loaded ${Object.keys(storedSessions).length} sessions from storage`); |
|
|
|
|
|
|
|
|
this.cleanupGhostSessionsFromStorage(storedSessions); |
|
|
|
|
|
|
|
|
const allSessions = { ...storedSessions }; |
|
|
if (appState.currentSessionData && appState.currentSessionData.id) { |
|
|
|
|
|
allSessions[appState.currentSessionData.id] = appState.currentSessionData; |
|
|
console.log(`[DEBUG] Including current session in list: ${appState.currentSessionData.id}`); |
|
|
} |
|
|
|
|
|
|
|
|
const sessionsArray = Object.values(allSessions).filter(session => { |
|
|
|
|
|
if (!session || !session.id) { |
|
|
console.log('[DEBUG] Filtering out session without ID:', session); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
const isGhostSession = ( |
|
|
(!session.title || session.title === 'Untitled Session' || session.title.trim() === '') && |
|
|
(!session.chatHistory || session.chatHistory.length === 0) && |
|
|
(!session.problemText || session.problemText.trim() === '') && |
|
|
(!session.image || session.image === null) |
|
|
); |
|
|
|
|
|
|
|
|
const isStuckSolvingSession = ( |
|
|
session.status === 'solving' && |
|
|
(!session.chatHistory || session.chatHistory.length === 0) && |
|
|
(!session.problemText || session.problemText.trim() === '') && |
|
|
Date.now() - new Date(session.createdAt || 0).getTime() > 60000 |
|
|
); |
|
|
|
|
|
if (isGhostSession) { |
|
|
console.log('[DEBUG] Filtering out ghost session:', session.id, session.title); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (isStuckSolvingSession) { |
|
|
console.log('[DEBUG] Filtering out stuck solving session:', session.id, session.title); |
|
|
return false; |
|
|
} |
|
|
|
|
|
return true; |
|
|
}).sort((a, b) => { |
|
|
|
|
|
const createdA = new Date(a.createdAt || 0); |
|
|
const createdB = new Date(b.createdAt || 0); |
|
|
|
|
|
if (createdB - createdA !== 0) { |
|
|
return createdB - createdA; |
|
|
} |
|
|
|
|
|
|
|
|
const usedA = new Date(a.lastUsed || 0); |
|
|
const usedB = new Date(b.lastUsed || 0); |
|
|
return usedB - usedA; |
|
|
}); |
|
|
|
|
|
console.log(`[DEBUG] Filtered and sorted ${sessionsArray.length} sessions`); |
|
|
|
|
|
|
|
|
const sessionElementsToAdd = []; |
|
|
|
|
|
|
|
|
sessionsArray.forEach(session => { |
|
|
const existingElement = sessionsList.querySelector(`[data-session-id="${session.id}"]`); |
|
|
|
|
|
if (existingElement) { |
|
|
|
|
|
this.updateSessionElement(existingElement, session); |
|
|
} else { |
|
|
|
|
|
const sessionElement = this.createSessionElement(session); |
|
|
if (sessionElement) { |
|
|
sessionElementsToAdd.push(sessionElement); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
sessionElementsToAdd.forEach(element => { |
|
|
sessionsList.appendChild(element); |
|
|
}); |
|
|
|
|
|
|
|
|
const orderedElements = []; |
|
|
sessionsArray.forEach(session => { |
|
|
const element = sessionsList.querySelector(`[data-session-id="${session.id}"]`); |
|
|
if (element) { |
|
|
orderedElements.push(element); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const existingElements = sessionsList.querySelectorAll('.session-item'); |
|
|
const validSessionIds = new Set(sessionsArray.map(s => s.id)); |
|
|
|
|
|
existingElements.forEach(element => { |
|
|
const elementSessionId = element.getAttribute('data-session-id'); |
|
|
if (!validSessionIds.has(elementSessionId)) { |
|
|
console.log(`[DEBUG] Removing orphaned session element: ${elementSessionId}`); |
|
|
element.remove(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
orderedElements.forEach(element => { |
|
|
sessionsList.appendChild(element); |
|
|
}); |
|
|
|
|
|
|
|
|
if (appState.selectedSessionId && appState.currentSessionData) { |
|
|
|
|
|
document.querySelectorAll('.session-item').forEach(item => { |
|
|
item.classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
|
|
|
const selectedElement = sessionsList.querySelector(`[data-session-id="${appState.selectedSessionId}"]`); |
|
|
if (selectedElement) { |
|
|
selectedElement.classList.add('selected'); |
|
|
console.log(`[DEBUG] Set selection on session: ${appState.selectedSessionId}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const totalSessions = sessionsArray.length; |
|
|
console.log(`[DEBUG] Total sessions for header: ${totalSessions}`); |
|
|
this.updateSessionsHeader(totalSessions); |
|
|
|
|
|
|
|
|
try { |
|
|
sessionElementsToAdd.forEach(element => { |
|
|
if (typeof feather !== 'undefined') { |
|
|
feather.replace(element); |
|
|
} |
|
|
}); |
|
|
} catch (e) { |
|
|
console.warn('[DEBUG] Could not replace feather icons in new session elements:', e); |
|
|
} |
|
|
|
|
|
|
|
|
this.removeStuckSpinnerElements(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[DEBUG] Error in refreshSessionsList:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
removeStuckSpinnerElements() { |
|
|
const sessionsList = domManager.getElement('sessionsList'); |
|
|
if (!sessionsList) return; |
|
|
|
|
|
const sessionElements = sessionsList.querySelectorAll('.session-item'); |
|
|
sessionElements.forEach(element => { |
|
|
const sessionId = element.getAttribute('data-session-id'); |
|
|
const icon = element.querySelector('[data-feather="loader"]'); |
|
|
|
|
|
|
|
|
if (icon && sessionId) { |
|
|
const sessions = storageManager.loadSessions(); |
|
|
const allSessions = { ...sessions }; |
|
|
if (appState.currentSessionData && appState.currentSessionData.id) { |
|
|
allSessions[appState.currentSessionData.id] = appState.currentSessionData; |
|
|
} |
|
|
|
|
|
const session = allSessions[sessionId]; |
|
|
if (!session || |
|
|
(!session.problemText && !session.chatHistory?.length && session.status !== 'solving')) { |
|
|
console.log('[DEBUG] Removing stuck spinner element:', sessionId); |
|
|
element.remove(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
updateSessionElement(element, session) { |
|
|
if (!element || !session) return; |
|
|
|
|
|
|
|
|
element.className = 'session-item'; |
|
|
if (session.status === 'completed') { |
|
|
element.classList.add('completed-session'); |
|
|
} else if (session.status === 'interrupted') { |
|
|
element.classList.add('interrupted-session'); |
|
|
} else if (session.status === 'solving') { |
|
|
element.classList.add('solving-session'); |
|
|
} |
|
|
|
|
|
|
|
|
let iconName = 'file-text'; |
|
|
if (session.status === 'completed') iconName = 'check-circle'; |
|
|
else if (session.status === 'interrupted') iconName = 'x-circle'; |
|
|
else if (session.status === 'solving') iconName = 'loader'; |
|
|
|
|
|
|
|
|
let timeAgo = 'Unknown time'; |
|
|
try { |
|
|
const displayDate = new Date(session.lastUsed || session.createdAt); |
|
|
timeAgo = this.getTimeAgo(displayDate); |
|
|
} catch (e) { |
|
|
console.warn('[DEBUG] Invalid date for session:', session.id, session.lastUsed, session.createdAt); |
|
|
} |
|
|
|
|
|
|
|
|
const messageCount = session.chatHistory ? session.chatHistory.length : 0; |
|
|
const messageText = messageCount === 1 ? 'message' : 'messages'; |
|
|
|
|
|
|
|
|
const title = session.title || 'Untitled Session'; |
|
|
const safeTitle = this.escapeHtml(title); |
|
|
|
|
|
|
|
|
const iconContainer = element.querySelector('.session-icon'); |
|
|
if (iconContainer) { |
|
|
const currentIcon = iconContainer.querySelector('i, svg'); |
|
|
const currentIconName = currentIcon ? currentIcon.getAttribute('data-feather') : 'unknown'; |
|
|
console.log(`[DEBUG] Updating session ${session.id} icon from ${currentIconName} to ${iconName} (status: ${session.status})`); |
|
|
|
|
|
|
|
|
iconContainer.innerHTML = `<i data-feather="${iconName}" style="width: 16px; height: 16px;"></i>`; |
|
|
console.log(`[DEBUG] Force replaced icon container for session ${session.id}`); |
|
|
} |
|
|
|
|
|
|
|
|
const titleElement = element.querySelector('.session-title'); |
|
|
const metaElement = element.querySelector('.session-meta'); |
|
|
if (titleElement) titleElement.textContent = title; |
|
|
if (metaElement) metaElement.textContent = `${timeAgo} • ${messageCount} ${messageText}`; |
|
|
|
|
|
|
|
|
const statusElement = element.querySelector('.session-status'); |
|
|
if (statusElement) { |
|
|
statusElement.className = `session-status ${session.status || 'active'}`; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
try { |
|
|
if (typeof feather !== 'undefined') { |
|
|
feather.replace(element); |
|
|
} |
|
|
console.log(`[DEBUG] Feather icons replaced for session ${session.id} with status ${session.status} -> ${iconName}`); |
|
|
} catch (e) { |
|
|
console.warn('[DEBUG] Could not replace feather icons in updated element:', e); |
|
|
} |
|
|
}, 10); |
|
|
} |
|
|
|
|
|
createSessionElement(session) { |
|
|
if (!session || !session.id) { |
|
|
console.error('[DEBUG] Invalid session data:', session); |
|
|
return null; |
|
|
} |
|
|
|
|
|
const sessionItem = document.createElement('div'); |
|
|
sessionItem.className = 'session-item'; |
|
|
sessionItem.setAttribute('data-session-id', session.id); |
|
|
|
|
|
|
|
|
if (session.status === 'completed') { |
|
|
sessionItem.classList.add('completed-session'); |
|
|
} else if (session.status === 'interrupted') { |
|
|
sessionItem.classList.add('interrupted-session'); |
|
|
} else if (session.status === 'solving') { |
|
|
sessionItem.classList.add('solving-session'); |
|
|
} |
|
|
|
|
|
|
|
|
let iconName = 'file-text'; |
|
|
if (session.status === 'completed') iconName = 'check-circle'; |
|
|
else if (session.status === 'interrupted') iconName = 'x-circle'; |
|
|
else if (session.status === 'solving') iconName = 'loader'; |
|
|
|
|
|
|
|
|
let timeAgo = 'Unknown time'; |
|
|
try { |
|
|
const displayDate = new Date(session.lastUsed || session.createdAt); |
|
|
timeAgo = this.getTimeAgo(displayDate); |
|
|
} catch (e) { |
|
|
console.warn('[DEBUG] Invalid date for session:', session.id, session.lastUsed, session.createdAt); |
|
|
} |
|
|
|
|
|
|
|
|
const messageCount = session.chatHistory ? session.chatHistory.length : 0; |
|
|
const messageText = messageCount === 1 ? 'message' : 'messages'; |
|
|
|
|
|
|
|
|
const title = session.title || 'Untitled Session'; |
|
|
const safeTitle = this.escapeHtml(title); |
|
|
|
|
|
sessionItem.innerHTML = ` |
|
|
<div class="session-icon"> |
|
|
<i data-feather="${iconName}" style="width: 16px; height: 16px;"></i> |
|
|
</div> |
|
|
<div class="session-info"> |
|
|
<div class="session-title">${safeTitle}</div> |
|
|
<div class="session-meta">${timeAgo} • ${messageCount} ${messageText}</div> |
|
|
</div> |
|
|
<div class="session-status ${session.status || 'active'}"> |
|
|
<span class="status-dot"></span> |
|
|
</div> |
|
|
<div class="session-actions"> |
|
|
<button class="session-download" title="Download this session"> |
|
|
<i data-feather="download" style="width: 12px; height: 12px;"></i> |
|
|
</button> |
|
|
<button class="session-delete" title="Delete session"> |
|
|
<i data-feather="x" style="width: 12px; height: 12px;"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
sessionItem.addEventListener('click', (e) => { |
|
|
try { |
|
|
console.log(`[DEBUG] Session item clicked: ${session.id}`, session.title); |
|
|
|
|
|
if (!e.target.closest('.session-delete') && !e.target.closest('.session-download')) { |
|
|
|
|
|
if (sessionItem.dataset.switching === 'true') { |
|
|
console.log('[DEBUG] Session switch already in progress, ignoring click'); |
|
|
return; |
|
|
} |
|
|
|
|
|
sessionItem.dataset.switching = 'true'; |
|
|
|
|
|
setTimeout(() => { |
|
|
this.switchToSession(session.id); |
|
|
sessionItem.dataset.switching = 'false'; |
|
|
}, 10); |
|
|
} else { |
|
|
console.log(`[DEBUG] Action button clicked, not switching session`); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('[DEBUG] Error in session click handler:', error); |
|
|
sessionItem.dataset.switching = 'false'; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const downloadButton = sessionItem.querySelector('.session-download'); |
|
|
downloadButton?.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
this.downloadSingleSession(session.id); |
|
|
}); |
|
|
|
|
|
|
|
|
const deleteButton = sessionItem.querySelector('.session-delete'); |
|
|
deleteButton?.addEventListener('click', (e) => { |
|
|
this.deleteSession(session.id, e); |
|
|
}); |
|
|
|
|
|
return sessionItem; |
|
|
} |
|
|
|
|
|
getTimeAgo(date) { |
|
|
if (!date) return 'Unknown time'; |
|
|
|
|
|
let dateObj; |
|
|
try { |
|
|
dateObj = new Date(date); |
|
|
if (isNaN(dateObj.getTime())) { |
|
|
return 'Invalid date'; |
|
|
} |
|
|
} catch (e) { |
|
|
return 'Invalid date'; |
|
|
} |
|
|
|
|
|
const now = new Date(); |
|
|
const diffMs = now - dateObj; |
|
|
|
|
|
|
|
|
if (diffMs < 0) { |
|
|
return 'Just now'; |
|
|
} |
|
|
|
|
|
const diffSecs = Math.floor(diffMs / 1000); |
|
|
const diffMins = Math.floor(diffMs / 60000); |
|
|
const diffHours = Math.floor(diffMs / 3600000); |
|
|
const diffDays = Math.floor(diffMs / 86400000); |
|
|
const diffWeeks = Math.floor(diffMs / (86400000 * 7)); |
|
|
const diffMonths = Math.floor(diffMs / (86400000 * 30)); |
|
|
|
|
|
if (diffSecs < 30) return 'Just now'; |
|
|
if (diffSecs < 60) return `${diffSecs}s ago`; |
|
|
if (diffMins < 60) return `${diffMins}m ago`; |
|
|
if (diffHours < 24) return `${diffHours}h ago`; |
|
|
if (diffDays < 7) return `${diffDays}d ago`; |
|
|
if (diffWeeks < 4) return `${diffWeeks}w ago`; |
|
|
if (diffMonths < 12) return `${diffMonths}mo ago`; |
|
|
|
|
|
|
|
|
return dateObj.toLocaleDateString(); |
|
|
} |
|
|
|
|
|
updateSessionsHeader(totalSessions) { |
|
|
const header = document.querySelector('.sessions-header .form-label'); |
|
|
if (!header) return; |
|
|
|
|
|
const baseText = 'Session History'; |
|
|
const sessionCount = Math.max(0, totalSessions || 0); |
|
|
|
|
|
if (sessionCount === 0) { |
|
|
header.innerHTML = ` |
|
|
<i data-feather="clock" style="width: 16px; height: 16px; margin-right: 8px;"></i> |
|
|
${baseText} |
|
|
`; |
|
|
} else if (sessionCount === 1) { |
|
|
header.innerHTML = ` |
|
|
<i data-feather="clock" style="width: 16px; height: 16px; margin-right: 8px;"></i> |
|
|
${baseText} (1 session) |
|
|
`; |
|
|
} else { |
|
|
header.innerHTML = ` |
|
|
<i data-feather="clock" style="width: 16px; height: 16px; margin-right: 8px;"></i> |
|
|
${baseText} (${sessionCount} sessions) |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
const headerElement = document.querySelector('.sessions-header'); |
|
|
if (headerElement && typeof feather !== 'undefined') { |
|
|
feather.replace(headerElement); |
|
|
} |
|
|
} catch (e) { |
|
|
console.warn('[DEBUG] Could not replace feather icons:', e); |
|
|
} |
|
|
} |
|
|
|
|
|
updateCurrentSessionDisplay() { |
|
|
|
|
|
|
|
|
|
|
|
this.refreshSessionsList(); |
|
|
} |
|
|
|
|
|
escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
|
|
|
handleSessionConnected(data) { |
|
|
console.log('[DEBUG] SessionManager.handleSessionConnected called with data:', JSON.stringify(data)); |
|
|
Logger.debug('Session', 'Session connected:', data); |
|
|
appState.currentSessionId = data.session_id; |
|
|
console.log('[DEBUG] Set appState.currentSessionId to:', data.session_id); |
|
|
|
|
|
const sessionInfoText = `Session: ${data.session_id.substring(0, 8)}`; |
|
|
console.log('[DEBUG] About to update session info to:', sessionInfoText); |
|
|
domManager.updateSessionInfo(sessionInfoText); |
|
|
|
|
|
console.log('[DEBUG] About to update status to: Connected - Ready to solve problems'); |
|
|
domManager.updateStatus('Connected - Ready to solve problems', 'success'); |
|
|
} |
|
|
|
|
|
|
|
|
handleSolveProblem(problemText, imageData) { |
|
|
|
|
|
if (!appState.currentSessionData || appState.selectedSessionId !== null) { |
|
|
|
|
|
console.log(`[DEBUG] Creating new session (previous status: ${appState.currentSessionData?.status || 'none'})`); |
|
|
appState.currentSessionData = this.createNewSession(problemText, imageData); |
|
|
appState.selectedSessionId = null; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.session-item').forEach(item => { |
|
|
item.classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
|
|
|
this.saveCurrentSessionToStorage(); |
|
|
console.log(`[DEBUG] New session created and saved with ID: ${appState.currentSessionData.id}`); |
|
|
|
|
|
this.refreshSessionsList(); |
|
|
} else { |
|
|
|
|
|
appState.currentSessionData.problemText = problemText; |
|
|
appState.currentSessionData.image = imageData; |
|
|
appState.currentSessionData.title = this.generateSessionTitle(problemText); |
|
|
|
|
|
this.saveCurrentSessionToStorage(); |
|
|
console.log(`[DEBUG] Updated and saved existing session: ${appState.currentSessionData.id}`); |
|
|
|
|
|
this.refreshSessionsList(); |
|
|
} |
|
|
|
|
|
return appState.currentSessionData.id; |
|
|
} |
|
|
|
|
|
|
|
|
handleSolvingStarted() { |
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.status = 'solving'; |
|
|
|
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
this.updateCurrentSessionDisplay(); |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
|
|
|
this.setInputsReadOnly('Cannot modify problem while solving is in progress'); |
|
|
|
|
|
|
|
|
if (appState.selectedSessionId && appState.currentSessionData.id === appState.selectedSessionId) { |
|
|
const sessionElement = document.querySelector(`[data-session-id="${appState.selectedSessionId}"]`); |
|
|
if (sessionElement) { |
|
|
sessionElement.classList.add('active-solving'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.startPeriodicSaving(); |
|
|
} |
|
|
} |
|
|
|
|
|
handleSolvingComplete() { |
|
|
console.log('[DEBUG] Handling solving completed - cleaning up UI and saving session'); |
|
|
|
|
|
|
|
|
this.stopPeriodicSaving(); |
|
|
|
|
|
|
|
|
messageManager.cleanupAllActiveIndicators(); |
|
|
|
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
appState.currentSessionData.status = 'completed'; |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
|
|
|
this.setInputsReadOnly('This session is completed. Start a new session to solve another problem.'); |
|
|
|
|
|
console.log(`[DEBUG] Session ${appState.currentSessionData.id} marked as completed and saved with ${appState.currentSessionData.chatHistory.length} messages`); |
|
|
} |
|
|
} |
|
|
|
|
|
handleSolvingInterrupted() { |
|
|
console.log('[DEBUG] Handling solving interrupted - cleaning up UI and saving session'); |
|
|
|
|
|
|
|
|
this.stopPeriodicSaving(); |
|
|
|
|
|
|
|
|
messageManager.cleanupAllActiveIndicators(); |
|
|
|
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
appState.currentSessionData.status = 'interrupted'; |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
|
|
|
this.setInputsReadOnly('This session was interrupted. Start a new session to solve another problem.'); |
|
|
|
|
|
console.log(`[DEBUG] Session ${appState.currentSessionData.id} marked as interrupted and saved with ${appState.currentSessionData.chatHistory.length} messages`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
handleSolvingError() { |
|
|
console.log('[DEBUG] Handling solving error - cleaning up UI and saving session'); |
|
|
|
|
|
|
|
|
this.stopPeriodicSaving(); |
|
|
|
|
|
|
|
|
messageManager.cleanupAllActiveIndicators(); |
|
|
|
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
appState.currentSessionData.status = 'interrupted'; |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
|
|
|
this.setInputsReadOnly('This session encountered an error. Start a new session to solve another problem.'); |
|
|
|
|
|
console.log(`[DEBUG] Session ${appState.currentSessionData.id} marked as interrupted due to error and saved with ${appState.currentSessionData.chatHistory.length} messages`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
emergencyCleanupAndSave() { |
|
|
console.log('[DEBUG] Emergency cleanup and save triggered'); |
|
|
|
|
|
try { |
|
|
|
|
|
messageManager.cleanupAllActiveIndicators(); |
|
|
|
|
|
|
|
|
if (appState.currentSessionData) { |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.status = 'interrupted'; |
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
this.refreshSessionsList(); |
|
|
|
|
|
console.log(`[DEBUG] Emergency save completed for session ${appState.currentSessionData.id} with ${appState.currentSessionData.chatHistory.length} messages`); |
|
|
} |
|
|
|
|
|
|
|
|
appState.isSolving = false; |
|
|
|
|
|
|
|
|
this.clearAndEnableInputs(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[DEBUG] Error during emergency cleanup:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
startPeriodicSaving() { |
|
|
|
|
|
this.stopPeriodicSaving(); |
|
|
|
|
|
|
|
|
this.periodicSaveInterval = setInterval(() => { |
|
|
if (appState.currentSessionData && appState.isSolving) { |
|
|
console.log('[DEBUG] Periodic save triggered during solving'); |
|
|
appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); |
|
|
appState.currentSessionData.lastUsed = new Date().toISOString(); |
|
|
this.saveCurrentSessionToStorage(); |
|
|
} else { |
|
|
|
|
|
this.stopPeriodicSaving(); |
|
|
} |
|
|
}, 10000); |
|
|
|
|
|
console.log('[DEBUG] Started periodic saving during solving'); |
|
|
} |
|
|
|
|
|
stopPeriodicSaving() { |
|
|
if (this.periodicSaveInterval) { |
|
|
clearInterval(this.periodicSaveInterval); |
|
|
this.periodicSaveInterval = null; |
|
|
console.log('[DEBUG] Stopped periodic saving'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
cleanupGhostSessionsFromStorage(sessions) { |
|
|
let deletedCount = 0; |
|
|
const sessionIds = Object.keys(sessions); |
|
|
|
|
|
sessionIds.forEach(sessionId => { |
|
|
const session = sessions[sessionId]; |
|
|
|
|
|
|
|
|
const isGhostSession = ( |
|
|
(!session.title || session.title === 'Untitled Session' || session.title.trim() === '') && |
|
|
(!session.chatHistory || session.chatHistory.length === 0) && |
|
|
(!session.problemText || session.problemText.trim() === '') && |
|
|
(!session.image || session.image === null) |
|
|
); |
|
|
|
|
|
const isStuckSolvingSession = ( |
|
|
session.status === 'solving' && |
|
|
(!session.chatHistory || session.chatHistory.length === 0) && |
|
|
(!session.problemText || session.problemText.trim() === '') && |
|
|
Date.now() - new Date(session.createdAt || 0).getTime() > 60000 |
|
|
); |
|
|
|
|
|
if (isGhostSession || isStuckSolvingSession) { |
|
|
console.log(`[DEBUG] Auto-removing ghost session from storage: ${sessionId}`); |
|
|
delete sessions[sessionId]; |
|
|
deletedCount++; |
|
|
} |
|
|
}); |
|
|
|
|
|
if (deletedCount > 0) { |
|
|
storageManager.saveSessions(sessions); |
|
|
console.log(`[DEBUG] Auto-cleaned ${deletedCount} ghost sessions from storage`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
cleanupGhostSessions() { |
|
|
console.log('[DEBUG] Starting ghost session cleanup'); |
|
|
|
|
|
try { |
|
|
const sessions = storageManager.loadSessions(); |
|
|
const sessionIds = Object.keys(sessions); |
|
|
let deletedCount = 0; |
|
|
|
|
|
sessionIds.forEach(sessionId => { |
|
|
const session = sessions[sessionId]; |
|
|
|
|
|
|
|
|
const isGhostSession = ( |
|
|
(!session.title || session.title === 'Untitled Session') && |
|
|
(!session.chatHistory || session.chatHistory.length === 0) && |
|
|
(!session.problemText || session.problemText.trim() === '') && |
|
|
session.status !== 'solving' |
|
|
); |
|
|
|
|
|
if (isGhostSession) { |
|
|
|
|
|
const sessionAge = Date.now() - new Date(session.createdAt || 0).getTime(); |
|
|
const oneHour = 60 * 60 * 1000; |
|
|
|
|
|
if (sessionAge > oneHour) { |
|
|
console.log(`[DEBUG] Cleaning up ghost session: ${sessionId}`); |
|
|
delete sessions[sessionId]; |
|
|
deletedCount++; |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
if (deletedCount > 0) { |
|
|
storageManager.saveSessions(sessions); |
|
|
console.log(`[DEBUG] Cleaned up ${deletedCount} ghost sessions`); |
|
|
} else { |
|
|
console.log('[DEBUG] No ghost sessions to clean up'); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('[DEBUG] Error during ghost session cleanup:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
clearAllSessionsEnhanced() { |
|
|
if (confirm('Are you sure you want to clear all session history? This cannot be undone.')) { |
|
|
try { |
|
|
|
|
|
appState.selectedSessionId = null; |
|
|
appState.currentSessionData = null; |
|
|
|
|
|
|
|
|
storageManager.clearAllSessions(); |
|
|
|
|
|
|
|
|
domManager.clearInputs(); |
|
|
imageHandler.clearImage(); |
|
|
messageManager.clearChatAndRestoreWelcome(); |
|
|
this.clearAndEnableInputs(); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.final-artifacts-compact').forEach(panel => { |
|
|
panel.remove(); |
|
|
}); |
|
|
|
|
|
|
|
|
const sessionsList = domManager.getElement('sessionsList'); |
|
|
if (sessionsList) { |
|
|
sessionsList.innerHTML = ''; |
|
|
} |
|
|
|
|
|
this.refreshSessionsList(); |
|
|
domManager.updateStatus('All sessions cleared', 'success'); |
|
|
Logger.debug('Session', 'All sessions cleared by user'); |
|
|
|
|
|
} catch (error) { |
|
|
Logger.error('Session', 'Error clearing sessions:', error); |
|
|
domManager.updateStatus('Error clearing sessions', 'error'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export const sessionManager = new SessionManager(); |