class EveAIChatWidget extends HTMLElement { static get observedAttributes() { return ['tenant-id', 'api-key', 'domain', 'language']; } constructor() { super(); this.socket = null; // Initialize socket to null this.attributesSet = false; // Flag to check if all attributes are set this.jwtToken = null; // Initialize jwtToken to null this.userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Detect user's timezone console.log('EveAIChatWidget constructor called'); } connectedCallback() { console.log('connectedCallback called'); this.innerHTML = this.getTemplate(); this.messagesArea = this.querySelector('.messages-area'); this.questionInput = this.querySelector('.question-area input'); this.sendButton = this.querySelector('.send-icon'); this.statusLine = this.querySelector('.status-line'); this.sendButton.addEventListener('click', () => this.handleSendMessage()); this.questionInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { this.handleSendMessage(); } }); if (this.areAllAttributesSet() && !this.socket) { console.log('Attributes already set in connectedCallback, initializing socket'); this.initializeSocket(); } } attributeChangedCallback(name, oldValue, newValue) { console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`); this.updateAttributes(); if (this.areAllAttributesSet() && !this.socket) { console.log('All attributes set in attributeChangedCallback, initializing socket'); this.attributesSet = true; this.initializeSocket(); } } updateAttributes() { this.tenantId = this.getAttribute('tenant-id'); this.apiKey = this.getAttribute('api-key'); this.domain = this.getAttribute('domain'); this.language = this.getAttribute('language'); console.log('Updated attributes:', { tenantId: this.tenantId, apiKey: this.apiKey, domain: this.domain, language: this.language }); } areAllAttributesSet() { const tenantId = this.getAttribute('tenant-id'); const apiKey = this.getAttribute('api-key'); const domain = this.getAttribute('domain'); const language = this.getAttribute('language'); console.log('Checking if all attributes are set:', { tenantId, apiKey, domain, language }); return tenantId && apiKey && domain && language; } initializeSocket() { if (this.socket) { console.log('Socket already initialized'); return; } if (!this.domain || this.domain === 'null') { console.error('Domain attribute is missing or invalid'); this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.'); return; } console.log(`Initializing socket connection to ${this.domain}`); // Ensure apiKey is passed in the query parameters this.socket = io(this.domain, { path: '/chat/socket.io/', transports: ['websocket', 'polling'], query: { tenantId: this.tenantId, apiKey: this.apiKey // Ensure apiKey is included here }, auth: { token: 'Bearer ' + this.apiKey // Ensure token is included here } }); console.log(`Finished initializing socket connection to ${this.domain}`); this.socket.on('connect', (data) => { console.log('Socket connected OK'); this.setStatusMessage('Connected to EveAI.'); }); this.socket.on('authenticated', (data) => { console.log('Authenticated event received: ', data); this.setStatusMessage('Authenticated.'); if (data.token) { this.jwtToken = data.token; // Store the JWT token received from the server } }); this.socket.on('connect_error', (err) => { console.error('Socket connection error:', err); this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.'); }); this.socket.on('connect_timeout', () => { console.error('Socket connection timeout'); this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.'); }); this.socket.on('disconnect', () => { console.log('Socket disconnected'); this.setStatusMessage('Disconnected from EveAI. Please refresh the page for further interaction.'); }); this.socket.on('bot_response', (data) => { if (data.tenantId === this.tenantId) { console.log('Initial response received:', data); console.log('Task ID received:', data.taskId); this.checkTaskStatus(data.taskId); this.setStatusMessage('Processing...'); } }); this.socket.on('task_status', (data) => { console.log('Task status received:', data.status); console.log('Task ID received:', data.taskId); console.log('Citations type:', typeof data.citations, 'Citations:', data.citations); if (data.status === 'pending') { this.updateProgress(); setTimeout(() => this.checkTaskStatus(data.taskId), 1000); // Poll every second } else if (data.status === 'success') { this.addBotMessage(data.answer, data.interaction_id, data.algorithm, data.citations); this.clearProgress(); // Clear progress indicator when done } else { this.setStatusMessage('Failed to process message.'); } }); } setStatusMessage(message) { this.statusLine.textContent = message; } updateProgress() { if (!this.statusLine.textContent) { this.statusLine.textContent = 'Processing...'; } else { this.statusLine.textContent += '.'; // Append a dot } } clearProgress() { this.statusLine.textContent = ''; this.toggleSendButton(false); // Re-enable and revert send button to outlined version } checkTaskStatus(taskId) { this.updateProgress(); this.socket.emit('check_task_status', { task_id: taskId }); } getTemplate() { return `
${text}
`; this.messagesArea.appendChild(message); this.messagesArea.scrollTop = this.messagesArea.scrollHeight; } handleFeedback(feedback, interactionId) { // Send feedback to the backend console.log('handleFeedback called'); if (!this.socket) { console.error('Socket is not initialized'); return; } if (!this.jwtToken) { console.error('JWT token is not available'); return; } console.log('Sending message to backend'); console.log(`Feedback for ${interactionId}: ${feedback}`); this.socket.emit('feedback', { tenantId: this.tenantId, token: this.jwtToken, feedback, interactionId }); this.setStatusMessage('Feedback sent.'); } addBotMessage(text, interactionId, algorithm = 'default', citations = []) { const message = document.createElement('div'); message.classList.add('message', 'bot'); let content = marked.parse(text); let citationsHtml = citations.map(url => `${url}`).join('${content}
${citationsHtml ? `${citationsHtml}
` : ''} `; this.messagesArea.appendChild(message); // Add event listeners for feedback buttons const thumbsUp = message.querySelector('i[data-feedback="up"]'); const thumbsDown = message.querySelector('i[data-feedback="down"]'); thumbsUp.addEventListener('click', () => this.toggleFeedback(thumbsUp, thumbsDown, 'up', interactionId)); thumbsDown.addEventListener('click', () => this.toggleFeedback(thumbsUp, thumbsDown, 'down', interactionId)); this.messagesArea.scrollTop = this.messagesArea.scrollHeight; } toggleFeedback(thumbsUp, thumbsDown, feedback, interactionId) { if (feedback === 'up') { thumbsUp.textContent = 'thumb_up'; // Change to filled icon thumbsUp.classList.remove('outlined'); thumbsUp.classList.add('filled'); thumbsDown.textContent = 'thumb_down_off_alt'; // Keep the other icon outlined thumbsDown.classList.add('outlined'); thumbsDown.classList.remove('filled'); } else { thumbsDown.textContent = 'thumb_down'; // Change to filled icon thumbsDown.classList.remove('outlined'); thumbsDown.classList.add('filled'); thumbsUp.textContent = 'thumb_up_off_alt'; // Keep the other icon outlined thumbsUp.classList.add('outlined'); thumbsUp.classList.remove('filled'); } // Send feedback to the backend this.handleFeedback(feedback, interactionId); } handleSendMessage() { console.log('handleSendMessage called'); const message = this.questionInput.value.trim(); if (message) { this.addUserMessage(message); this.questionInput.value = ''; this.sendMessageToBackend(message); this.toggleSendButton(true); // Disable and change send button to filled version } } sendMessageToBackend(message) { console.log('sendMessageToBackend called'); if (!this.socket) { console.error('Socket is not initialized'); return; } if (!this.jwtToken) { console.error('JWT token is not available'); return; } console.log('Sending message to backend'); this.socket.emit('user_message', { tenantId: this.tenantId, token: this.jwtToken, message, language: this.language, timezone: this.userTimezone }); this.setStatusMessage('Processing started ...') } toggleSendButton(isProcessing) { if (isProcessing) { this.sendButton.textContent = 'send'; // Filled send icon this.sendButton.classList.remove('outlined'); this.sendButton.classList.add('filled'); this.sendButton.classList.add('disabled'); // Add disabled class for styling this.sendButton.style.pointerEvents = 'none'; // Disable click events } else { this.sendButton.textContent = 'send'; // Outlined send icon this.sendButton.classList.add('outlined'); this.sendButton.classList.remove('filled'); this.sendButton.classList.remove('disabled'); // Remove disabled class this.sendButton.style.pointerEvents = 'auto'; // Re-enable click events } } } customElements.define('eveai-chat-widget', EveAIChatWidget);