/** * Session Manager - Handles session UI and management functionality */ 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; // Clean up ghost sessions on startup this.cleanupGhostSessions(); this.setupEventListeners(); this.refreshSessionsList(); this.isInitialized = true; Logger.debug('Session', 'Session manager initialized'); } setupEventListeners() { // Session management listeners 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()); // Import file input handler domManager.getElement('importSessionsInput')?.addEventListener('change', (e) => this.handleImportFile(e)); // Session header click 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'); // Save current session before resetting if we have one if (appState.currentSessionData) { console.log('[DEBUG] Saving current session before reset'); appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); // Update the current state 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(); } // Reset session management state appState.selectedSessionId = null; appState.currentSessionData = null; // Clear visual selection document.querySelectorAll('.session-item').forEach(item => { item.classList.remove('selected'); }); // Clear inputs and make them editable this.clearAndEnableInputs(); // Clear chat and restore welcome message properly messageManager.clearChatAndRestoreWelcome(); // Clear any existing feedback panels from previous sessions if (window.interactiveFeedback) { window.interactiveFeedback.removeFeedbackPanel(); window.interactiveFeedback.removeRestoreButton(); } // Clear any final solution artifacts panels document.querySelectorAll('.final-artifacts-compact').forEach(panel => { panel.remove(); }); // Clear per-session custom rules import('./settings-manager.js').then(({ settingsManager }) => { settingsManager.clearPerSessionRules(); }); this.updateCurrentSessionDisplay(); console.log('[DEBUG] Reset to new session state completed'); } clearAndEnableInputs() { // Clear inputs domManager.clearInputs(); // Enable and reset input field to editable state 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 = ''; } // Remove any read-only messages this.removeReadOnlyMessage(); // Replace feather icons 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; } // Add read-only message this.showReadOnlyMessage(); } showReadOnlyMessage() { // Remove any existing message first 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 = ` This session is read-only. Click "Start New Session" to solve a new problem. `; // Add message after button group 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) { // A session is considered "used" (read-only) only if it has been // finished or explicitly interrupted. This mirrors the logic that // lives in the inline implementation inside index.html. Active or // in-progress ("solving") sessions remain editable even if they have // chat history. 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; } // Validate file type if (!file.name.endsWith('.json')) { domManager.updateStatus('Please select a JSON file', 'warning'); return; } try { domManager.updateStatus('Importing sessions...', 'info'); // Read file content const fileContent = await this.readFileAsText(file); // Import sessions with merge enabled, no duplicates overwrite by default const result = await storageManager.importSessions(fileContent, { merge: true, overwriteDuplicates: false }); // Handle results 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`); // Show detailed summary if there were duplicates 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 { // Clear the file input 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'); } } // Session data management saveCurrentSessionToStorage() { if (!appState.currentSessionData) { console.log('[DEBUG] No current session data to save'); return; } // Get current state from UI const problemText = domManager.getElement('questionInput')?.value.trim() || ''; const imageElement = domManager.getElement('imagePreview'); const image = imageElement?.style.display !== 'none' ? imageElement.src : null; // Update session data appState.currentSessionData.problemText = problemText; appState.currentSessionData.image = image; appState.currentSessionData.title = this.generateSessionTitle(problemText); // Always update lastUsed when saving appState.currentSessionData.lastUsed = new Date().toISOString(); // Get current chat history (this is critical for persistence) 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)}...`); } // Save to storage 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'; } // Take first meaningful part of the problem text const cleaned = problemText.trim().replace(/\s+/g, ' '); const maxLength = 50; if (cleaned.length <= maxLength) { return cleaned; } // Try to break at word boundaries 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(); // Validate that we have meaningful content before creating a session 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}`); // Critical: Handle edge case - prevent switching while solving if (appState.isSolving) { domManager.updateStatus('Cannot switch sessions while solving. Please stop the current task first.', 'warning'); return; } // Prevent multiple simultaneous switches if (window.sessionSwitchInProgress) { console.log('[DEBUG] Session switch already in progress, ignoring'); return; } window.sessionSwitchInProgress = true; try { // Save current session state if we have one if (appState.currentSessionData) { console.log('[DEBUG] Saving current session state before switching'); appState.currentSessionData.chatHistory = messageManager.getCurrentChatHistory(); // Update the current state 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(); } // Load the selected session - use the same logic as refreshSessionsList for consistency let sessions = storageManager.loadSessions(); console.log(`[DEBUG] Loaded sessions from storage:`, Object.keys(sessions)); // Create the same combined sessions that the UI uses 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)); // Debug: Show details about each available session 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`); // Update state WITHOUT updating lastUsed to prevent reorganization on view appState.selectedSessionId = sessionId; appState.currentSessionData = { ...session }; // Clear ALL selections first, then set the correct one document.querySelectorAll('.session-item').forEach(item => { item.classList.remove('selected'); }); // Set selection on the clicked session 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}`); // Try again after a brief delay in case DOM is updating 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); } // Load session data into UI const questionInput = domManager.getElement('questionInput'); if (questionInput) { questionInput.value = session.problemText || ''; } // Check if session is used/read-only const isUsedSession = this.isSessionUsed(session); if (isUsedSession) { // Make session read-only 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 { // Enable editing for fresh sessions this.clearAndEnableInputs(); console.log(`[DEBUG] Session ${sessionId} is editable (status: ${session.status})`); } // Load image if present imageHandler.loadSessionImage(session.image); // Load chat history 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 { // Always clear the switch lock 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 { // Load sessions from storage const sessions = storageManager.loadSessions(); console.log(`[DEBUG] Loaded ${Object.keys(sessions).length} sessions from storage`); // Delete 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 this is the current session in memory, clear it if (appState.currentSessionData && appState.currentSessionData.id === sessionId) { console.log(`[DEBUG] Deleting current session from memory: ${sessionId}`); appState.currentSessionData = null; appState.selectedSessionId = null; // Clear inputs and UI domManager.clearInputs(); imageHandler.clearImage(); messageManager.clearChatAndRestoreWelcome(); this.clearAndEnableInputs(); // Clear any final solution artifacts panels document.querySelectorAll('.final-artifacts-compact').forEach(panel => { panel.remove(); }); } // If this was the selected session, clear selection if (appState.selectedSessionId === sessionId) { console.log(`[DEBUG] Clearing selected session: ${sessionId}`); appState.selectedSessionId = null; } // Force remove the DOM element immediately to provide instant feedback const sessionElement = document.querySelector(`[data-session-id="${sessionId}"]`); if (sessionElement) { sessionElement.remove(); console.log(`[DEBUG] Removed DOM element for session: ${sessionId}`); } // Refresh the sessions list 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 { // Ensure current session is saved to storage before refreshing list 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`); // Automatically clean up ghost sessions from storage this.cleanupGhostSessionsFromStorage(storedSessions); // Combine stored sessions with current session if it exists const allSessions = { ...storedSessions }; if (appState.currentSessionData && appState.currentSessionData.id) { // Always include current session in the list, overriding stored version allSessions[appState.currentSessionData.id] = appState.currentSessionData; console.log(`[DEBUG] Including current session in list: ${appState.currentSessionData.id}`); } // Convert sessions object to array and sort by creation time (newest first) const sessionsArray = Object.values(allSessions).filter(session => { // Filter out invalid sessions and ghost sessions if (!session || !session.id) { console.log('[DEBUG] Filtering out session without ID:', session); return false; } // Filter out ghost sessions (much more aggressive filtering) 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) ); // Also filter out sessions with "solving" status but no actual content and are old 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 // 1 minute old ); 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) => { // Primary sort: creation time (newest first) const createdA = new Date(a.createdAt || 0); const createdB = new Date(b.createdAt || 0); if (createdB - createdA !== 0) { return createdB - createdA; } // Secondary sort (tie-breaker): lastUsed (newest first) 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`); // Track which session elements need to be created const sessionElementsToAdd = []; // Update existing elements and identify new ones sessionsArray.forEach(session => { const existingElement = sessionsList.querySelector(`[data-session-id="${session.id}"]`); if (existingElement) { // Update existing element in place this.updateSessionElement(existingElement, session); } else { // Create new element const sessionElement = this.createSessionElement(session); if (sessionElement) { sessionElementsToAdd.push(sessionElement); } } }); // Add new elements in sorted order sessionElementsToAdd.forEach(element => { sessionsList.appendChild(element); }); // Reorder elements according to sort order const orderedElements = []; sessionsArray.forEach(session => { const element = sessionsList.querySelector(`[data-session-id="${session.id}"]`); if (element) { orderedElements.push(element); } }); // Remove orphaned DOM elements (sessions that no longer exist in data) 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(); } }); // Reorder DOM elements orderedElements.forEach(element => { sessionsList.appendChild(element); }); // Update selection after reordering if (appState.selectedSessionId && appState.currentSessionData) { // Clear all selections first document.querySelectorAll('.session-item').forEach(item => { item.classList.remove('selected'); }); // Set selection on the currently selected session const selectedElement = sessionsList.querySelector(`[data-session-id="${appState.selectedSessionId}"]`); if (selectedElement) { selectedElement.classList.add('selected'); console.log(`[DEBUG] Set selection on session: ${appState.selectedSessionId}`); } } // Update session count in header const totalSessions = sessionsArray.length; console.log(`[DEBUG] Total sessions for header: ${totalSessions}`); this.updateSessionsHeader(totalSessions); // Replace feather icons for newly added session elements only 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); } // Final cleanup: ensure no stuck spinner sessions remain in the UI this.removeStuckSpinnerElements(); } catch (error) { console.error('[DEBUG] Error in refreshSessionsList:', error); } } // Remove any UI elements that still have spinners but shouldn't 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 element has a spinner icon but no corresponding valid session data, remove it 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; // Update status-based styling element.className = 'session-item'; // Reset classes 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'); } // Determine icon based on status 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'; // Handle date safely 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); } // Handle message count safely const messageCount = session.chatHistory ? session.chatHistory.length : 0; const messageText = messageCount === 1 ? 'message' : 'messages'; // Handle title safely const title = session.title || 'Untitled Session'; const safeTitle = this.escapeHtml(title); // Update icon - force complete refresh for reliability 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})`); // Always force refresh the icon to ensure proper updating iconContainer.innerHTML = ``; console.log(`[DEBUG] Force replaced icon container for session ${session.id}`); } // Update title and meta const titleElement = element.querySelector('.session-title'); const metaElement = element.querySelector('.session-meta'); if (titleElement) titleElement.textContent = title; if (metaElement) metaElement.textContent = `${timeAgo} • ${messageCount} ${messageText}`; // Update status class const statusElement = element.querySelector('.session-status'); if (statusElement) { statusElement.className = `session-status ${session.status || 'active'}`; } // Replace feather icons for this element only with a small delay to ensure DOM update 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); // Add status-based styling 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'); } // Determine icon based on status 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'; // Handle date safely 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); } // Handle message count safely const messageCount = session.chatHistory ? session.chatHistory.length : 0; const messageText = messageCount === 1 ? 'message' : 'messages'; // Handle title safely const title = session.title || 'Untitled Session'; const safeTitle = this.escapeHtml(title); sessionItem.innerHTML = `