document.addEventListener('DOMContentLoaded', () => { // --- DOM Elements --- const chatArea = document.getElementById('chat-area'); const closeSidebarBtn = document.getElementById('close-sidebar-btn'); const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); const newChatBtn = document.getElementById('new-chat-btn'); const chatMessages = document.getElementById('chat-messages'); const chatHistoryList = document.getElementById('chat-history-list'); const menuToggle = document.getElementById('menu-toggle'); const appContainer = document.getElementById('app-container'); const chatTitle = document.getElementById('chat-title'); const imageUploadBtn = document.getElementById('image-upload-btn'); const imageUploadInput = document.getElementById('image-upload-input'); const imagePreviewContainer = document.getElementById('image-preview-container'); const imagePreview = document.getElementById('image-preview'); const removeImageBtn = document.getElementById('remove-image-btn'); // Add search and filter elements let searchInput, clearAllBtn, userStatsDiv; // --- State --- let currentChatId = null; let currentUserId = null; let conversationsCache = {}; let selectedImageFile = null; let imgbbApiKey = ''; let firebase = null; let firebaseDB = null; let useFirebase = false; // --- User Management --- const generateUserFingerprint = () => { // Create a device fingerprint based on available browser info const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillText('Device fingerprint', 2, 2); const fingerprint = [ navigator.userAgent, navigator.language, navigator.platform, navigator.cookieEnabled, navigator.doNotTrack, screen.width + 'x' + screen.height, screen.colorDepth, new Date().getTimezoneOffset(), canvas.toDataURL(), navigator.hardwareConcurrency || 'unknown', navigator.deviceMemory || 'unknown' ].join('|'); // Create hash return hashCode(fingerprint).toString(); }; const hashCode = (str) => { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash); }; const initializeUser = async () => { // Try to get existing user ID from storage let userId = localStorage.getItem('easyfarms_user_id'); if (!userId) { // Generate new user ID based on device fingerprint const deviceFingerprint = generateUserFingerprint(); const timestamp = Date.now(); const randomPart = Math.random().toString(36).substr(2, 8); userId = `user_${deviceFingerprint}_${timestamp}_${randomPart}`; localStorage.setItem('easyfarms_user_id', userId); console.log('Generated new user ID:', userId); } else { console.log('Using existing user ID:', userId); } currentUserId = userId; // Also save to Firebase if available if (useFirebase && firebaseDB) { try { await saveUserData(userId, { created_at: new Date().toISOString(), device_fingerprint: generateUserFingerprint(), last_seen: new Date().toISOString() }); } catch (error) { console.error('Failed to save user data to Firebase:', error); } } return userId; }; const saveUserData = async (userId, userData) => { try { if (useFirebase && firebaseDB) { const { setDoc, doc } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); await setDoc(doc(firebaseDB, 'users', userId), { ...userData, updatedAt: new Date().toISOString() }); console.log('User data saved to Firebase:', userId); } else { // Fallback to localStorage const users = JSON.parse(localStorage.getItem('easyfarms_users') || '{}'); users[userId] = { ...userData, updatedAt: new Date().toISOString() }; localStorage.setItem('easyfarms_users', JSON.stringify(users)); console.log('User data saved to localStorage:', userId); } } catch (error) { console.error('Failed to save user data:', error); } }; // --- Firebase Configuration --- const initializeFirebase = async () => { try { if (typeof firebase !== 'undefined' && window.firebaseConfig) { const { initializeApp } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-app.js'); const { getFirestore, collection, doc, setDoc, getDoc, getDocs, deleteDoc } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); const app = initializeApp(window.firebaseConfig); firebaseDB = getFirestore(app); useFirebase = true; console.log('Firebase initialized successfully'); return { setDoc, getDoc, getDocs, deleteDoc, collection, doc }; } else { console.log('Firebase not configured, using localStorage'); return null; } } catch (error) { console.error('Firebase initialization failed, falling back to localStorage:', error); return null; } }; // --- Storage Management with User Isolation --- const saveSessionData = async (chatId, data) => { try { if (useFirebase && firebaseDB) { const { setDoc, doc } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); await setDoc(doc(firebaseDB, 'user_sessions', `${currentUserId}_${chatId}`), { ...data, userId: currentUserId, chatId: chatId, updatedAt: new Date().toISOString() }); console.log('Session saved to Firebase:', chatId, 'for user:', currentUserId); } else { // Fallback to localStorage with user isolation const userSessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); userSessions[chatId] = { ...data, userId: currentUserId, updatedAt: new Date().toISOString() }; localStorage.setItem(`easyfarms_sessions_${currentUserId}`, JSON.stringify(userSessions)); console.log('Session saved to localStorage:', chatId, 'for user:', currentUserId); } } catch (error) { console.error('Failed to save session data:', error); // Fallback to localStorage const userSessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); userSessions[chatId] = data; localStorage.setItem(`easyfarms_sessions_${currentUserId}`, JSON.stringify(userSessions)); } }; const loadSessionData = async (chatId) => { try { if (useFirebase && firebaseDB) { const { getDoc, doc } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); const docRef = doc(firebaseDB, 'user_sessions', `${currentUserId}_${chatId}`); const docSnap = await getDoc(docRef); if (docSnap.exists()) { console.log('Session loaded from Firebase:', chatId); return docSnap.data(); } } else { // Fallback to localStorage const userSessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); if (userSessions[chatId]) { console.log('Session loaded from localStorage:', chatId); return userSessions[chatId]; } } return null; } catch (error) { console.error('Failed to load session data:', error); // Fallback to localStorage const userSessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); return userSessions[chatId] || null; } }; const loadAllSessions = async () => { try { if (useFirebase && firebaseDB) { const { getDocs, collection, query, where } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); const q = query(collection(firebaseDB, 'user_sessions'), where('userId', '==', currentUserId)); const querySnapshot = await getDocs(q); const sessions = {}; querySnapshot.forEach((doc) => { const data = doc.data(); sessions[data.chatId] = data; }); console.log('All sessions loaded from Firebase for user:', currentUserId); return sessions; } else { // Fallback to localStorage const sessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); console.log('All sessions loaded from localStorage for user:', currentUserId); return sessions; } } catch (error) { console.error('Failed to load all sessions:', error); // Fallback to localStorage return JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); } }; const deleteSessionData = async (chatId) => { try { if (useFirebase && firebaseDB) { const { deleteDoc, doc } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); await deleteDoc(doc(firebaseDB, 'user_sessions', `${currentUserId}_${chatId}`)); console.log('Session deleted from Firebase:', chatId); } else { // Fallback to localStorage const userSessions = JSON.parse(localStorage.getItem(`easyfarms_sessions_${currentUserId}`) || '{}'); delete userSessions[chatId]; localStorage.setItem(`easyfarms_sessions_${currentUserId}`, JSON.stringify(userSessions)); console.log('Session deleted from localStorage:', chatId); } } catch (error) { console.error('Failed to delete session data:', error); } }; // --- Initialization --- const init = async () => { if (!messageInput || !sendBtn || !imageUploadBtn || !imagePreviewContainer) { console.error("Critical UI elements not found. Check HTML IDs."); return; } // Initialize Firebase firebase = await initializeFirebase(); // Initialize user await initializeUser(); await fetchConfig(); await loadCachedSessions(); await renderChatHistoryFromAPI(); setupSearchAndFilter(); setupUserStats(); // Event Listeners sendBtn.addEventListener('click', sendMessage); messageInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); imageUploadBtn.addEventListener('click', () => imageUploadInput.click()); imageUploadInput.addEventListener('change', handleImageSelect); removeImageBtn.addEventListener('click', removeSelectedImage); // Sidebar Listeners newChatBtn.addEventListener('click', () => { startNewChat(); closeSidebar(); }); menuToggle.addEventListener('click', (event) => { event.stopPropagation(); appContainer.classList.toggle('sidebar-visible'); }); closeSidebarBtn.addEventListener('click', (event) => { event.stopPropagation(); closeSidebar(); }); chatArea.addEventListener('click', () => { if (appContainer.classList.contains('sidebar-visible')) { closeSidebar(); } }); // Update last seen periodically setInterval(updateLastSeen, 30000); // Every 30 seconds console.log('EasyFarms Chat initialized with user authentication for user:', currentUserId); }; const fetchConfig = async () => { try { const response = await fetch('/config'); const config = await response.json(); imgbbApiKey = config.imgbb_api_key; if (!imgbbApiKey) { console.error("ImgBB API Key is missing."); } } catch (error) { console.error('Failed to fetch config:', error); } }; const setupUserStats = () => { // Create user stats div if (!userStatsDiv) { userStatsDiv = document.createElement('div'); userStatsDiv.className = 'user-stats'; userStatsDiv.style.cssText = ` padding: 10px; margin: 10px 0; background: #f8f9fa; border-radius: 4px; font-size: 12px; color: #666; border-top: 1px solid #eee; `; // Insert at the bottom of sidebar chatHistoryList.parentNode.appendChild(userStatsDiv); } updateUserStats(); }; const updateUserStats = async () => { try { const response = await fetch(`/users/${currentUserId}/stats`); if (response.ok) { const stats = await response.json(); userStatsDiv.innerHTML = `
Your Stats
Chats: ${stats.total_sessions || 0}
Messages: ${stats.total_messages || 0}
Recent: ${stats.recent_sessions_24h || 0}
`; } } catch (error) { console.error('Failed to update user stats:', error); // Show basic local stats const sessions = await loadAllSessions(); const sessionCount = Object.keys(sessions).length; userStatsDiv.innerHTML = `
Local Stats
Chats: ${sessionCount}
User: ${currentUserId.substr(0, 12)}...
`; } }; const updateLastSeen = async () => { try { await saveUserData(currentUserId, { last_seen: new Date().toISOString() }); } catch (error) { console.error('Failed to update last seen:', error); } }; const setupSearchAndFilter = () => { // Create search input if it doesn't exist if (!searchInput) { searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = 'Search chats...'; searchInput.className = 'search-input'; searchInput.style.cssText = ` width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; `; chatHistoryList.parentNode.insertBefore(searchInput, chatHistoryList); } // Create clear all button if it doesn't exist if (!clearAllBtn) { clearAllBtn = document.createElement('button'); clearAllBtn.textContent = 'Clear All My Chats'; clearAllBtn.className = 'clear-all-btn'; clearAllBtn.style.cssText = ` width: 100%; padding: 8px; margin: 5px 0; background: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; `; searchInput.parentNode.insertBefore(clearAllBtn, chatHistoryList); } // Add event listeners searchInput.addEventListener('input', filterChats); clearAllBtn.addEventListener('click', confirmClearAllChats); }; const filterChats = () => { const searchTerm = searchInput.value.toLowerCase(); const chatItems = chatHistoryList.querySelectorAll('.chat-history-item'); chatItems.forEach(item => { const title = item.textContent.toLowerCase(); const shouldShow = title.includes(searchTerm); item.style.display = shouldShow ? 'block' : 'none'; }); console.log('Filtered chats with term:', searchTerm); }; const confirmClearAllChats = () => { if (confirm('Are you sure you want to delete all your chats? This action cannot be undone.')) { clearAllChats(); } }; const clearAllChats = async () => { try { // Delete all user chats from backend const response = await fetch(`/users/${currentUserId}/chats`, { method: 'DELETE' }); if (response.ok) { console.log('All chats deleted from backend for user:', currentUserId); } else { console.error('Failed to delete all chats from backend'); } // Clear local storage const allSessions = await loadAllSessions(); const chatIds = Object.keys(allSessions); for (const chatId of chatIds) { await deleteSessionData(chatId); } // Clear UI and cache conversationsCache = {}; chatHistoryList.innerHTML = ''; startNewChat(); updateUserStats(); console.log('All chats cleared successfully for user:', currentUserId); } catch (error) { console.error('Failed to clear all chats:', error); } }; // --- Core Chat Functions --- const sendMessage = async () => { const messageText = messageInput.value.trim(); if (!messageText && !selectedImageFile) return; // Display user message immediately displayMessage({ role: 'user', content: messageText, imageUrl: selectedImageFile }); const loadingIndicator = displayMessage({ role: 'assistant', content: 'Thinking...', isLoading: true }); try { let permanentImageUrl = null; if (selectedImageFile) { permanentImageUrl = await uploadImageToImgBB(selectedImageFile); } // Prepare request data with user ID const requestData = { query: messageText, session_id: currentChatId, user_id: currentUserId }; if (permanentImageUrl) { requestData.image_url = permanentImageUrl; } // Send to API endpoint const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestData) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('Chat response:', data); // Remove loading indicator chatMessages.removeChild(loadingIndicator); // Display assistant response displayMessage({ role: 'assistant', content: data.response, message_id: data.assistant_message_id }); // Update cache and UI await updateCacheWithNewSystem( data.chat_id, { content: messageText, imageUrl: permanentImageUrl, message_id: data.user_message_id }, { content: data.response, message_id: data.assistant_message_id }, data.is_new_chat ); // Update stats updateUserStats(); } catch (error) { console.error('Error sending message:', error); // Remove loading indicator and show error chatMessages.removeChild(loadingIndicator); // Display mock error response (don't save to database) displayMessage({ role: 'assistant', content: "I apologize, but I'm having trouble connecting right now. Please check your internet connection and try again. Your chat session is still active.", isError: true }); } finally { messageInput.value = ''; messageInput.style.height = 'auto'; removeSelectedImage(); } }; const uploadImageToImgBB = async (imageFile) => { if (!imgbbApiKey) throw new Error("ImgBB API Key not configured."); const formData = new FormData(); formData.append('image', imageFile); formData.append('key', imgbbApiKey); const response = await fetch('https://api.imgbb.com/1/upload', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { return result.data.url; } else { throw new Error(result.error.message || 'Image upload failed.'); } }; // --- Chat Management --- const startNewChat = () => { currentChatId = null; chatMessages.innerHTML = `

EasyFarms Assistant

User: ${currentUserId.substr(0, 16)}...

`; chatTitle.textContent = "New Chat"; updateActiveChatItem(); console.log('Started new chat for user:', currentUserId); }; const switchChat = async (chatId) => { console.log('Switching to chat:', chatId, 'for user:', currentUserId); currentChatId = chatId; chatMessages.innerHTML = ''; // Check cache first if (conversationsCache[chatId] && conversationsCache[chatId].messages) { conversationsCache[chatId].messages.forEach(displayMessage); chatTitle.textContent = conversationsCache[chatId].title || "Chat"; } else { // Load from API const loading = displayMessage({ role: 'assistant', content: 'Loading chat history...', isLoading: true }); try { const response = await fetch(`/chat/${chatId}/messages?user_id=${currentUserId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const messages = await response.json(); console.log('Loaded messages for chat:', chatId, messages); // Update cache if (!conversationsCache[chatId]) conversationsCache[chatId] = {}; conversationsCache[chatId].messages = messages; // Save to storage await saveSessionData(chatId, conversationsCache[chatId]); // Remove loading and display messages chatMessages.removeChild(loading); messages.forEach(displayMessage); // Set title from first user message or use cached title const sessionData = await loadSessionData(chatId); chatTitle.textContent = sessionData?.title || conversationsCache[chatId]?.title || "Chat"; } catch (error) { console.error('Failed to load chat history:', error); chatMessages.removeChild(loading); displayMessage({ role: 'assistant', content: "Failed to load chat history. Please try again.", isError: true }); } } updateActiveChatItem(); closeSidebar(); }; const deleteChat = async (chatId, chatItem) => { if (!confirm('Are you sure you want to delete this chat?')) return; try { // Delete from backend const response = await fetch(`/chat/${chatId}?user_id=${currentUserId}`, { method: 'DELETE' }); if (response.ok) { console.log('Chat deleted from backend:', chatId); } else { console.error('Failed to delete chat from backend:', chatId); } // Delete from storage await deleteSessionData(chatId); // Remove from cache delete conversationsCache[chatId]; // Remove from UI chatItem.remove(); // If this was the current chat, start a new one if (currentChatId === chatId) { startNewChat(); } console.log('Chat deleted successfully:', chatId); updateUserStats(); } catch (error) { console.error('Error deleting chat:', error); alert('Failed to delete chat. Please try again.'); } }; const updateCacheWithNewSystem = async (chatId, userTurn, assistantTurn, isNewChat) => { const previousChatId = currentChatId; currentChatId = chatId; if (isNewChat || !conversationsCache[chatId]) { const title = (userTurn.content || "Image Query").substring(0, 30) + '...'; conversationsCache[chatId] = { title, messages: [], created_at: new Date().toISOString(), user_id: currentUserId }; // Add to UI const item = createChatHistoryItem(chatId, title); chatHistoryList.prepend(item); console.log('Created new chat:', chatId, 'for user:', currentUserId); } // Create message objects with IDs const userMessage = { role: 'user', content: userTurn.content, message_id: userTurn.message_id, timestamp: new Date().toISOString() }; if (userTurn.imageUrl) userMessage.imageUrl = userTurn.imageUrl; const assistantMessage = { role: 'assistant', content: assistantTurn.content, message_id: assistantTurn.message_id, timestamp: new Date().toISOString() }; // Add to cache conversationsCache[chatId].messages.push(userMessage, assistantMessage); conversationsCache[chatId].updated_at = new Date().toISOString(); conversationsCache[chatId].user_id = currentUserId; // Save to storage await saveSessionData(chatId, conversationsCache[chatId]); updateActiveChatItem(); chatTitle.textContent = conversationsCache[chatId].title; console.log('Updated cache for chat:', chatId, 'user:', currentUserId); }; const createChatHistoryItem = (chatId, title) => { const item = document.createElement('div'); item.className = 'chat-history-item'; item.dataset.sessionId = chatId; // Create title element const titleElement = document.createElement('span'); titleElement.textContent = title; titleElement.className = 'chat-title'; // Create delete button const deleteBtn = document.createElement('button'); deleteBtn.innerHTML = '×'; deleteBtn.className = 'delete-chat-btn'; deleteBtn.style.cssText = ` float: right; background: none; border: none; color: #999; cursor: pointer; font-size: 18px; padding: 0; width: 20px; height: 20px; line-height: 18px; `; deleteBtn.title = 'Delete chat'; // Add event listeners titleElement.addEventListener('click', () => switchChat(chatId)); deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteChat(chatId, item); }); item.appendChild(titleElement); item.appendChild(deleteBtn); return item; }; const loadCachedSessions = async () => { try { conversationsCache = await loadAllSessions(); console.log('Loaded cached sessions for user:', currentUserId, 'Count:', Object.keys(conversationsCache).length); } catch (error) { console.error('Failed to load cached sessions:', error); conversationsCache = {}; } }; const displayMessage = (message) => { const { role, content, imageUrl, isLoading, isError, message_id } = message; const sender = role || message.sender; const messageDiv = document.createElement('div'); messageDiv.classList.add('message', `${sender}-message`); if (message_id) { messageDiv.dataset.messageId = message_id; console.log('Displayed message with ID:', message_id); } let htmlContent = ''; const imageSrc = (typeof imageUrl === 'object' && imageUrl instanceof File) ? URL.createObjectURL(imageUrl) : imageUrl; if (imageSrc) { htmlContent += `User upload`; } if (content) { htmlContent += marked.parse(content); } messageDiv.innerHTML = htmlContent || (isLoading ? '...' : ''); if (isLoading) messageDiv.classList.add('loading'); if (isError) messageDiv.classList.add('error'); chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; return messageDiv; }; // --- Image Preview Handling --- const handleImageSelect = (event) => { const file = event.target.files[0]; if (file) { selectedImageFile = file; imagePreview.src = URL.createObjectURL(file); imagePreviewContainer.style.display = 'block'; console.log('Image selected:', file.name); } }; const removeSelectedImage = () => { selectedImageFile = null; imageUploadInput.value = ''; imagePreviewContainer.style.display = 'none'; imagePreview.src = '#'; console.log('Image removed'); }; // --- API Integration --- const renderChatHistoryFromAPI = async () => { try { const response = await fetch(`/chats?user_id=${currentUserId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const sessions = await response.json(); console.log('Loaded chat sessions from API for user:', currentUserId, sessions.length); chatHistoryList.innerHTML = ''; // Sort by updated_at (most recent first) sessions.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); sessions.forEach(session => { // Update cache if (!conversationsCache[session.session_id]) { conversationsCache[session.session_id] = {}; } conversationsCache[session.session_id].title = session.title; conversationsCache[session.session_id].message_count = session.message_count; conversationsCache[session.session_id].created_at = session.created_at; conversationsCache[session.session_id].updated_at = session.updated_at; conversationsCache[session.session_id].user_id = currentUserId; // Create UI item const item = createChatHistoryItem(session.session_id, session.title); chatHistoryList.appendChild(item); }); // Save updated cache for (const [chatId, data] of Object.entries(conversationsCache)) { if (data.user_id === currentUserId) { await saveSessionData(chatId, data); } } updateActiveChatItem(); updateUserStats(); } catch (error) { console.error("Failed to render chat history from API:", error); // Fallback to local cache console.log('Falling back to local cache for user:', currentUserId); for (const [chatId, data] of Object.entries(conversationsCache)) { if (data.title && data.user_id === currentUserId) { const item = createChatHistoryItem(chatId, data.title); chatHistoryList.appendChild(item); } } } }; const updateActiveChatItem = () => { document.querySelectorAll('.chat-history-item').forEach(item => { item.classList.toggle('active', item.dataset.sessionId === currentChatId); }); }; const closeSidebar = () => appContainer.classList.remove('sidebar-visible'); // --- User Account Management --- const exportUserData = async () => { try { const response = await fetch(`/users/${currentUserId}/export`); if (response.ok) { const userData = await response.json(); const dataStr = JSON.stringify(userData, null, 2); const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); const exportFileDefaultName = `easyfarms_data_${currentUserId.substr(0, 8)}_${new Date().toISOString().split('T')[0]}.json`; const linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); linkElement.click(); console.log('User data exported successfully'); } else { throw new Error('Export failed'); } } catch (error) { console.error('Failed to export user data:', error); alert('Failed to export data. Please try again.'); } }; const deleteUserAccount = async () => { if (!confirm('Are you sure you want to delete your account? This will permanently delete all your chats and data.')) { return; } if (!confirm('This action cannot be undone. Type "DELETE" to confirm:') || prompt('Type "DELETE" to confirm account deletion:') !== 'DELETE') { return; } try { const response = await fetch(`/users/${currentUserId}`, { method: 'DELETE' }); if (response.ok) { // Clear all local data localStorage.removeItem('easyfarms_user_id'); localStorage.removeItem(`easyfarms_sessions_${currentUserId}`); localStorage.removeItem(`easyfarms_users`); // Clear Firebase data if applicable if (useFirebase && firebaseDB) { try { const { deleteDoc, doc, collection, getDocs, query, where } = await import('https://www.gstatic.com/firebasejs/9.0.0/firebase-firestore.js'); // Delete user document await deleteDoc(doc(firebaseDB, 'users', currentUserId)); // Delete user sessions const q = query(collection(firebaseDB, 'user_sessions'), where('userId', '==', currentUserId)); const querySnapshot = await getDocs(q); querySnapshot.forEach(async (doc) => { await deleteDoc(doc.ref); }); } catch (firebaseError) { console.error('Failed to clean Firebase data:', firebaseError); } } alert('Account deleted successfully. Refreshing page...'); location.reload(); } else { throw new Error('Account deletion failed'); } } catch (error) { console.error('Failed to delete account:', error); alert('Failed to delete account. Please try again.'); } }; const createUserMenu = () => { // Create user menu button const userMenuBtn = document.createElement('button'); userMenuBtn.innerHTML = '⚙️'; userMenuBtn.title = 'User Settings'; userMenuBtn.style.cssText = ` position: fixed; top: 10px; right: 10px; background: #007bff; color: white; border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; font-size: 16px; z-index: 1000; `; // Create user menu dropdown const userMenu = document.createElement('div'); userMenu.style.cssText = ` position: fixed; top: 55px; right: 10px; background: white; border: 1px solid #ddd; border-radius: 4px; padding: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: none; z-index: 1001; min-width: 200px; `; userMenu.innerHTML = `
User Settings
ID: ${currentUserId.substr(0, 16)}...
`; document.body.appendChild(userMenuBtn); document.body.appendChild(userMenu); // Toggle menu userMenuBtn.addEventListener('click', (e) => { e.stopPropagation(); userMenu.style.display = userMenu.style.display === 'none' ? 'block' : 'none'; }); // Close menu when clicking outside document.addEventListener('click', () => { userMenu.style.display = 'none'; }); // Menu actions document.getElementById('export-data-btn').addEventListener('click', exportUserData); document.getElementById('delete-account-btn').addEventListener('click', deleteUserAccount); }; // --- Enhanced Error Handling --- window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); // Don't show error to user for now, just log it }); window.addEventListener('error', (event) => { console.error('Global error:', event.error); // Don't show error to user for now, just log it }); // --- Session Recovery --- const attemptSessionRecovery = async () => { try { // Try to recover any unsaved messages const unsavedMessages = localStorage.getItem(`unsaved_messages_${currentUserId}`); if (unsavedMessages) { const messages = JSON.parse(unsavedMessages); console.log('Found unsaved messages, attempting recovery:', messages); // Display recovered messages messages.forEach(msg => { displayMessage({ role: msg.role, content: `[RECOVERED] ${msg.content}`, isError: true }); }); // Clear unsaved messages localStorage.removeItem(`unsaved_messages_${currentUserId}`); } } catch (error) { console.error('Session recovery failed:', error); } }; const saveUnsavedMessage = (role, content) => { try { const unsavedMessages = JSON.parse(localStorage.getItem(`unsaved_messages_${currentUserId}`) || '[]'); unsavedMessages.push({ role, content, timestamp: new Date().toISOString() }); // Keep only last 5 unsaved messages if (unsavedMessages.length > 5) { unsavedMessages.splice(0, unsavedMessages.length - 5); } localStorage.setItem(`unsaved_messages_${currentUserId}`, JSON.stringify(unsavedMessages)); } catch (error) { console.error('Failed to save unsaved message:', error); } }; // --- Performance Monitoring --- const logPerformance = () => { if (performance && performance.timing) { const perfData = performance.timing; const loadTime = perfData.loadEventEnd - perfData.navigationStart; console.log(`Page load time: ${loadTime}ms`); // Log to backend if needed // fetch('/analytics/performance', { // method: 'POST', // body: JSON.stringify({ user_id: currentUserId, load_time: loadTime }) // }); } }; // --- Initialize Everything --- // Add user menu after initialization const setupUserInterface = () => { createUserMenu(); attemptSessionRecovery(); logPerformance(); }; // Start the Application init().then(() => { setupUserInterface(); console.log('EasyFarms Chat fully initialized for user:', currentUserId); }).catch((error) => { console.error('Failed to initialize application:', error); document.body.innerHTML = `

Failed to Load EasyFarms Chat

Please refresh the page and try again.

Error: ${error.message}

`; }); });