/** * Universal Utilities for All AI Agent Pages * Compatible with: Task Agent, Email Agent, Calendar Agent, Trade Intelligence * Enhanced with offline handling, performance optimizations, and better error handling */ // Create Utils object if it doesn't exist window.Utils = window.Utils || {}; // Time utilities Utils.time = { updateLastUpdated: function(elementId) { const element = document.getElementById(elementId); if (element) { element.textContent = new Date().toLocaleTimeString(); } }, formatTimestamp: function(date) { if (!date) return 'Unknown'; try { return new Date(date).toLocaleString(); } catch (e) { return date; } } }; // Form utilities Utils.form = { setInputValue: function(elementId, value) { const element = document.getElementById(elementId); if (element) { element.value = value; } }, getInputValue: function(elementId) { const element = document.getElementById(elementId); return element ? element.value : ''; }, clearInput: function(elementId) { const element = document.getElementById(elementId); if (element) { element.value = ''; } } }; // Button utilities Utils.button = { setLoading: function(elementId, text, iconClass) { const element = document.getElementById(elementId); if (element) { element.innerHTML = ` ${text}`; element.disabled = true; } }, restore: function(elementId, text, iconClass) { const element = document.getElementById(elementId); if (element) { element.innerHTML = ` ${text}`; element.disabled = false; } } }; // Event utilities Utils.events = { onEnterKey: function(elementId, callback) { const element = document.getElementById(elementId); if (element) { element.addEventListener('keypress', function(e) { if (e.key === 'Enter') { callback(); } }); } } }; // Stats utilities Utils.stats = { increment: function(statName) { console.log('Incrementing stat:', statName); const element = document.getElementById(statName); if (element) { const currentValue = parseInt(element.textContent) || 0; element.textContent = currentValue + 1; } }, update: function(statName, value) { const element = document.getElementById(statName); if (element) { element.textContent = value; } } }; // Enhanced API utilities with offline handling Utils.api = { // Enhanced request with better offline detection async request(url, options = {}) { try { console.log('🔄 Making API request to:', url); // Check if browser is online if (!navigator.onLine) { throw new Error('OFFLINE'); } const defaultOptions = { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, // Add timeout to detect slow/failed connections signal: AbortSignal.timeout(30000) // 30 second timeout }; const response = await fetch(url, { ...defaultOptions, ...options }); if (!response.ok) { throw new Error(`HTTP_${response.status}`); } const data = await response.json(); console.log('✅ API request successful'); return data; } catch (error) { console.error('❌ API Request failed:', error); // Handle different types of errors with user-friendly messages let userMessage = ''; let errorType = 'error'; if (error.message === 'OFFLINE' || !navigator.onLine) { userMessage = '📱 You appear to be offline. Please check your internet connection and try again.'; errorType = 'warning'; } else if (error.name === 'TimeoutError' || error.message.includes('timeout')) { userMessage = '⏱️ Request timed out. The server may be busy. Please try again.'; errorType = 'warning'; } else if (error.message.includes('Failed to fetch')) { userMessage = '🌐 Unable to connect to server. Please check your connection and try again.'; errorType = 'warning'; } else if (error.message.includes('HTTP_5')) { userMessage = '🔧 Server error (5xx). Please try again in a few moments.'; errorType = 'error'; } else if (error.message.includes('HTTP_4')) { userMessage = '⚠️ Request error (4xx). Please check your input and try again.'; errorType = 'warning'; } else { userMessage = '❌ Something went wrong. Please try again.'; errorType = 'error'; } // Show user-friendly toast Utils.toast.show(userMessage, errorType); // Re-throw for handling by calling function throw error; } }, // Quick method for GET requests with parameters async get(url, params = {}) { const urlWithParams = new URL(url); Object.keys(params).forEach(key => { if (params[key] !== undefined && params[key] !== null) { urlWithParams.searchParams.append(key, params[key]); } }); return this.request(urlWithParams.toString()); }, // Test connection method async testConnection() { try { await fetch('https://httpbin.org/status/200', { method: 'HEAD', signal: AbortSignal.timeout(5000) }); return true; } catch (error) { return false; } } }; // Network status detection Utils.network = { isOnline: navigator.onLine, init() { // Listen for online/offline events window.addEventListener('online', () => { this.isOnline = true; Utils.toast.success('🌐 Connection restored!'); console.log('✅ Back online'); }); window.addEventListener('offline', () => { this.isOnline = false; Utils.toast.warning('📱 You are now offline. Some features may not work.'); console.log('⚠️ Gone offline'); }); }, // Check if we should even attempt API calls canMakeRequests() { if (!this.isOnline) { Utils.toast.warning('📱 No internet connection. Please check your network.'); return false; } return true; } }; // Performance utilities Utils.performance = { // Debounce function for search inputs (prevents too many API calls) debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // Throttle function for scroll events throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, // Optimize search inputs automatically optimizeSearchInputs() { const searchInputs = document.querySelectorAll('#query, #searchQuery, #taskSearchInput, #taskCommand, #calendarCommand'); searchInputs.forEach(input => { if (input) { // Remove any existing event listeners to avoid duplicates const newInput = input.cloneNode(true); input.parentNode.replaceChild(newInput, input); // Add optimized event listener newInput.addEventListener('input', this.debounce((e) => { // Only trigger if user has typed something meaningful if (e.target.value.length >= 2) { console.log('Optimized search triggered for:', e.target.value); } }, 300)); } }); } }; // Modal utilities Utils.modal = { showAI: function(content) { const modal = document.getElementById('aiModal'); const responseDiv = document.getElementById('aiResponse'); if (modal && responseDiv) { responseDiv.innerHTML = content; modal.classList.remove('hidden'); } }, hideAI: function() { const modal = document.getElementById('aiModal'); if (modal) { modal.classList.add('hidden'); } }, show: function(modalId, content) { const modal = document.getElementById(modalId); if (modal) { if (content) { const contentDiv = modal.querySelector('.modal-content') || modal.querySelector('#aiResponse') || modal.querySelector('.p-6:last-child'); if (contentDiv) { contentDiv.innerHTML = content; } } modal.classList.remove('hidden'); } }, hide: function(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('hidden'); } } }; // Enhanced toast notifications Utils.toast = { show: function(message, type = 'info') { // Remove existing toasts document.querySelectorAll('.utils-toast').forEach(toast => toast.remove()); const toast = document.createElement('div'); toast.className = 'utils-toast fixed top-4 right-4 z-50 max-w-sm transform transition-all duration-300 translate-x-full'; const styles = { success: { bg: 'bg-green-500', icon: 'fas fa-check-circle' }, error: { bg: 'bg-red-500', icon: 'fas fa-exclamation-circle' }, warning: { bg: 'bg-yellow-500', icon: 'fas fa-exclamation-triangle' }, info: { bg: 'bg-blue-500', icon: 'fas fa-info-circle' } }; const style = styles[type] || styles.info; toast.innerHTML = `