/** * Utility functions for the AI Chat Application * * This module provides various utility functions for: * - Text processing and formatting * - Date/time operations * - Local storage management * - Event handling * - Performance monitoring * - Debouncing and throttling */ // Text Processing Utilities class TextUtils { // Truncate text to specified length static truncate(text, maxLength, suffix = '...') { if (!text || text.length <= maxLength) return text; return text.substring(0, maxLength - suffix.length) + suffix; } // Count words in text static wordCount(text) { if (!text) return 0; return text.trim().split(/\s+/).filter(word => word.length > 0).length; } // Estimate reading time (assuming 200 words per minute) static estimateReadingTime(text) { const words = this.wordCount(text); const minutes = Math.ceil(words / 200); return minutes; } // Clean and normalize text static normalize(text) { if (!text) return ''; return text .trim() .replace(/\s+/g, ' ') .replace(/[^\w\s.,!?;:-]/g, ''); } // Extract hashtags from text static extractHashtags(text) { const hashtagRegex = /#[\w]+/g; return text.match(hashtagRegex) || []; } // Extract mentions from text static extractMentions(text) { const mentionRegex = /@[\w]+/g; return text.match(mentionRegex) || []; } // Convert text to slug format static slugify(text) { return text .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } // Highlight search terms in text static highlight(text, searchTerm, className = 'highlight') { if (!searchTerm) return text; const regex = new RegExp(`(${searchTerm})`, 'gi'); return text.replace(regex, `$1`); } // Strip HTML tags from text static stripHtml(html) { const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; } // Convert markdown-like syntax to HTML static simpleMarkdown(text) { return text .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/\n/g, '
'); } } // Date and Time Utilities class DateUtils { // Format date relative to now (e.g., "2 minutes ago") static formatRelative(date) { const now = new Date(); const diffInSeconds = Math.floor((now - date) / 1000); if (diffInSeconds < 60) return 'just now'; if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`; if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`; return date.toLocaleDateString(); } // Format date for display static formatDisplay(date, options = {}) { const defaultOptions = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return date.toLocaleDateString(undefined, { ...defaultOptions, ...options }); } // Get start of day static startOfDay(date = new Date()) { const start = new Date(date); start.setHours(0, 0, 0, 0); return start; } // Get end of day static endOfDay(date = new Date()) { const end = new Date(date); end.setHours(23, 59, 59, 999); return end; } // Check if date is today static isToday(date) { const today = new Date(); return this.startOfDay(date).getTime() === this.startOfDay(today).getTime(); } // Check if date is yesterday static isYesterday(date) { const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); return this.startOfDay(date).getTime() === this.startOfDay(yesterday).getTime(); } // Get time zone offset static getTimezoneOffset() { return new Date().getTimezoneOffset(); } // Parse various date formats static parseDate(dateString) { // Try different date formats const formats = [ /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD /^\d{2}\/\d{2}\/\d{4}$/, // MM/DD/YYYY /^\d{2}-\d{2}-\d{4}$/, // MM-DD-YYYY ]; for (const format of formats) { if (format.test(dateString)) { const date = new Date(dateString); if (!isNaN(date.getTime())) { return date; } } } return null; } } // Local Storage Utilities class StorageUtils { // Safe JSON parse static safeJsonParse(str, fallback = null) { try { return JSON.parse(str); } catch (error) { console.warn('Failed to parse JSON:', error); return fallback; } } // Safe JSON stringify static safeJsonStringify(obj, fallback = '{}') { try { return JSON.stringify(obj); } catch (error) { console.warn('Failed to stringify JSON:', error); return fallback; } } // Get item from localStorage with fallback static getItem(key, fallback = null) { try { const item = localStorage.getItem(key); return item !== null ? this.safeJsonParse(item, fallback) : fallback; } catch (error) { console.warn('Failed to get localStorage item:', error); return fallback; } } // Set item in localStorage static setItem(key, value) { try { localStorage.setItem(key, this.safeJsonStringify(value)); return true; } catch (error) { console.warn('Failed to set localStorage item:', error); return false; } } // Remove item from localStorage static removeItem(key) { try { localStorage.removeItem(key); return true; } catch (error) { console.warn('Failed to remove localStorage item:', error); return false; } } // Clear all localStorage data static clear() { try { localStorage.clear(); return true; } catch (error) { console.warn('Failed to clear localStorage:', error); return false; } } // Get storage usage static getStorageUsage() { let total = 0; for (let key in localStorage) { if (localStorage.hasOwnProperty(key)) { total += localStorage[key].length + key.length; } } return total; } // Check if storage is available static isAvailable() { try { const test = '__storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (error) { return false; } } } // Event Handling Utilities class EventUtils { // Debounce function calls static debounce(func, wait, immediate = false) { let timeout; return function executedFunction(...args) { const later = () => { timeout = null; if (!immediate) func.apply(this, args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(this, args); }; } // Throttle function calls static throttle(func, limit) { let inThrottle; return function executedFunction(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Add event listener with cleanup static addListener(element, event, handler, options = {}) { element.addEventListener(event, handler, options); return () => element.removeEventListener(event, handler, options); } // Create event emitter static createEmitter() { const listeners = new Map(); return { on(event, callback) { if (!listeners.has(event)) { listeners.set(event, []); } listeners.get(event).push(callback); }, off(event, callback) { const eventListeners = listeners.get(event); if (eventListeners) { const index = eventListeners.indexOf(callback); if (index > -1) { eventListeners.splice(index, 1); } } }, emit(event, data) { const eventListeners = listeners.get(event); if (eventListeners) { eventListeners.forEach(callback => callback(data)); } }, once(event, callback) { const onceWrapper = (data) => { callback(data); this.off(event, onceWrapper); }; this.on(event, onceWrapper); } }; } // Check if element is in viewport static isInViewport(element) { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } // Smooth scroll to element static scrollToElement(element, options = {}) { const defaultOptions = { behavior: 'smooth', block: 'start', inline: 'nearest' }; element.scrollIntoView({ ...defaultOptions, ...options }); } } // Performance Utilities class PerformanceUtils { // Measure function execution time static measureTime(func, ...args) { const start = performance.now(); const result = func.apply(this, args); const end = performance.now(); console.log(`Function executed in ${end - start} milliseconds`); return result; } // Measure async function execution time static async measureAsyncTime(func, ...args) { const start = performance.now(); const result = await func.apply(this, args); const end = performance.now(); console.log(`Async function executed in ${end - start} milliseconds`); return result; } // Create performance observer static observePerformance(callback) { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver(callback); observer.observe({ entryTypes: ['measure', 'navigation', 'resource'] }); return observer; } return null; } // Memory usage information static getMemoryInfo() { if (performance.memory) { return { used: Math.round(performance.memory.usedJSHeapSize / 1048576), total: Math.round(performance.memory.totalJSHeapSize / 1048576), limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576) }; } return null; } // Request idle callback wrapper static onIdle(callback, options = {}) { if ('requestIdleCallback' in window) { return requestIdleCallback(callback, options); } else { return setTimeout(callback, 0); } } // Cancel idle callback static cancelIdle(id) { if ('cancelIdleCallback' in window) { cancelIdleCallback(id); } else { clearTimeout(id); } } } // Validation Utilities class ValidationUtils { // Validate email format static isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Validate URL format static isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } // Check if string is empty or whitespace static isEmpty(str) { return !str || str.trim().length === 0; } // Validate message content static isValidMessage(content) { if (typeof content !== 'string') return false; if (this.isEmpty(content)) return false; if (content.length > 10000) return false; // Max length check return true; } // Check for profanity (basic implementation) static containsProfanity(text) { const profanityList = ['spam', 'scam']; // Add more as needed const lowerText = text.toLowerCase(); return profanityList.some(word => lowerText.includes(word)); } // Validate JSON structure static isValidJson(str) { try { JSON.parse(str); return true; } catch { return false; } } } // Random Utilities class RandomUtils { // Generate random ID static generateId(length = 8) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } // Generate UUID v4 static generateUuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // Get random element from array static randomElement(array) { return array[Math.floor(Math.random() * array.length)]; } // Shuffle array static shuffle(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } // Generate random color static randomColor() { return `#${Math.floor(Math.random()*16777215).toString(16)}`; } } // Export utilities const Utils = { Text: TextUtils, Date: DateUtils, Storage: StorageUtils, Event: EventUtils, Performance: PerformanceUtils, Validation: ValidationUtils, Random: RandomUtils }; // Make available globally window.Utils = Utils; // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = Utils; }