/* * widget.js - Shilte AI Multi-Tenant Chat Widget * Deployed to: https://cdn.shilte.ai/widget.js */ class ShilteChatWidget { constructor(businessId, apiUrl) { this.businessId = businessId; this.apiUrl = apiUrl; this.isGreetingSent = false; // The chat history to be maintained across the session this.chatHistory = this.loadHistory(); if (!this.businessId || !this.apiUrl) { console.error("Shilte AI Widget requires both data-business-id and data-api-url attributes."); return; } this.init(); } // --- Core UI Setup --- init() { // 1. Create a container element and attach a Shadow DOM for isolation const container = document.createElement('div'); container.id = 'shilte-ai-chat-container'; document.body.appendChild(container); const shadowRoot = container.attachShadow({ mode: 'open' }); // 2. Add Styles (Inline for simplicity, would be a separate CSS file in production) shadowRoot.innerHTML = `
💬
Shilte AI Support ×
`; this.ui = { window: shadowRoot.getElementById('chat-window'), body: shadowRoot.getElementById('chat-body'), input: shadowRoot.getElementById('user-input'), sendButton: shadowRoot.getElementById('send-btn'), bubble: shadowRoot.getElementById('chat-bubble'), closeButton: shadowRoot.getElementById('close-btn'), }; this.addEventListeners(); this.loadChatHistory(); this.loadAutoGreeting(); } addEventListeners() { this.ui.bubble.addEventListener('click', () => this.toggleChat(true)); this.ui.closeButton.addEventListener('click', () => this.toggleChat(false)); this.ui.sendButton.addEventListener('click', () => this.handleUserInput()); this.ui.input.addEventListener('keypress', (e) => { if (e.key === 'Enter') this.handleUserInput(); }); } toggleChat(open) { if (open) { this.ui.window.classList.add('open'); this.ui.input.focus(); this.scrollToBottom(); } else { this.ui.window.classList.remove('open'); } } // --- History & Display Utilities --- loadHistory() { try { const key = `shilte_chat_history_${this.businessId}`; const history = localStorage.getItem(key); return history ? JSON.parse(history) : []; } catch (e) { console.warn("Failed to load chat history from localStorage.", e); return []; } } saveHistory() { try { const key = `shilte_chat_history_${this.businessId}`; localStorage.setItem(key, JSON.stringify(this.chatHistory)); } catch (e) { console.warn("Failed to save chat history to localStorage.", e); } } loadChatHistory() { this.chatHistory.forEach(msg => { if (msg.role !== 'system') { // Don't display system roles this.displayMessage(msg.text, msg.role === 'user' ? 'user' : 'bot'); } }); this.scrollToBottom(); } displayMessage(text, role) { const msgDiv = document.createElement('div'); msgDiv.className = role === 'user' ? 'message-user' : 'message-bot'; msgDiv.textContent = text; this.ui.body.appendChild(msgDiv); this.scrollToBottom(); return msgDiv; } scrollToBottom() { this.ui.body.scrollTop = this.ui.body.scrollHeight; } // --- API & Core Logic --- async loadAutoGreeting() { if (this.chatHistory.length > 0 || this.isGreetingSent) return; try { // New cached endpoint for fast greeting response const response = await fetch(`${this.apiUrl}/crm/shilte/greeting`, { method: 'POST', // Use POST if the greeting relies on the current prompt/settings headers: { 'X-Business-ID': this.businessId, 'Content-Type': 'application/json' }, body: JSON.stringify({ businessID: this.businessId }) // May need a dummy body }); if (response.ok) { const data = await response.json(); // Display the greeting after a short, friendly delay setTimeout(() => { this.displayMessage(data.message, 'bot'); this.chatHistory.push({ role: 'assistant', text: data.message }); this.saveHistory(); this.isGreetingSent = true; }, 1000); // 1.0 second delay } } catch (error) { console.error("Error fetching auto-greeting:", error); // Fallback: If network fails, display a generic message this.displayMessage("Hello! How can I assist you today?", 'bot'); } } async handleUserInput() { const userQuery = this.ui.input.value.trim(); if (!userQuery) return; // 1. Display and save user message this.displayMessage(userQuery, 'user'); this.chatHistory.push({ role: 'user', text: userQuery }); this.saveHistory(); this.ui.input.value = ''; // 2. Show Typing Indicator const typingIndicator = this.displayMessage("Bot is typing...", 'bot'); try { // 3. Call the main chat generation endpoint const response = await fetch(`${this.apiUrl}/crm/shilte/generate`, { method: 'POST', headers: { 'X-Business-ID': this.businessId, // CRITICAL: Tenant Isolation 'Content-Type': 'application/json' }, body: JSON.stringify({ user_query: userQuery, // Send entire history for context chat_history: this.chatHistory.map(msg => ({ role: msg.role, parts: [{ text: msg.text }] })) }) }); if (!response.ok) { throw new Error(`API returned status ${response.status}`); } const data = await response.json(); // 4. Remove typing indicator and display bot response this.ui.body.removeChild(typingIndicator); const botResponseText = data.response; this.displayMessage(botResponseText, 'bot'); this.chatHistory.push({ role: 'assistant', text: botResponseText }); this.saveHistory(); } catch (error) { this.ui.body.removeChild(typingIndicator); this.displayMessage("Sorry, I'm having trouble connecting right now. Please try again later.", 'bot'); console.error("Chat API Error:", error); } } } // --- Initialization Logic --- document.addEventListener('DOMContentLoaded', () => { // Find the script tag that loaded this file const scriptTag = document.querySelector('script[data-business-id][data-api-url]'); if (scriptTag) { const businessId = scriptTag.getAttribute('data-business-id'); const apiUrl = scriptTag.getAttribute('data-api-url'); // Initialize the widget new ShilteChatWidget(businessId, apiUrl); } else { console.error("Shilte AI Widget: Cannot find the script tag with required attributes."); } });