chatbot / static /chat.js
Moderator404's picture
Upload 39 files
41bd0b8 verified
Raw
History Blame Contribute Delete
12.1 kB
// Chat functionality for Too Many Cables customer service
class ChatInterface {
constructor() {
this.chatMessages = document.getElementById('chat-messages');
this.messageInput = document.getElementById('message-input');
this.sendButton = document.getElementById('send-button');
this.chatForm = document.getElementById('chat-form');
this.clearButton = document.getElementById('clear-chat');
this.typingIndicator = document.getElementById('typing-indicator');
this.statusDot = document.getElementById('status-dot');
this.statusText = document.getElementById('status-text');
this.agentStatus = document.getElementById('agent-status');
this.conversationId = null;
this.isConnected = false;
this.init();
}
init() {
this.checkConnection();
this.setupEventListeners();
this.loadConversationHistory();
}
setupEventListeners() {
// Form submission
if (this.chatForm) {
this.chatForm.addEventListener('submit', (e) => {
e.preventDefault();
this.sendMessage();
});
}
// Clear chat
if (this.clearButton) {
this.clearButton.addEventListener('click', () => {
this.clearConversation();
});
}
// End conversation
const endConversationButton = document.getElementById('end-conversation-button');
if (endConversationButton) {
console.log('End conversation button found and event listener added');
endConversationButton.addEventListener('click', () => {
this.endConversation();
});
} else {
console.log('End conversation button NOT found in DOM');
}
// Auto-resize textarea
if (this.messageInput) {
this.messageInput.addEventListener('input', () => {
this.autoResizeTextarea();
this.toggleSendButton();
});
// Enter key handling
this.messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
}
}
async checkConnection() {
try {
const response = await fetch('/api/health');
if (response.ok) {
this.updateConnectionStatus(true);
} else {
this.updateConnectionStatus(false);
}
} catch (error) {
console.error('Connection check failed:', error);
this.updateConnectionStatus(false);
}
}
updateConnectionStatus(connected) {
this.isConnected = connected;
if (this.statusDot && this.statusText) {
if (connected) {
this.statusDot.className = 'status-dot connected';
this.statusText.textContent = 'Connected';
} else {
this.statusDot.className = 'status-dot disconnected';
this.statusText.textContent = 'Connection issues';
}
}
if (this.agentStatus) {
this.agentStatus.textContent = connected ? 'Online' : 'Offline';
}
}
autoResizeTextarea() {
if (this.messageInput) {
this.messageInput.style.height = 'auto';
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
}
}
toggleSendButton() {
if (this.sendButton && this.messageInput) {
this.sendButton.disabled = this.messageInput.value.trim().length === 0;
}
}
async sendMessage() {
const message = this.messageInput.value.trim();
if (!message || !this.isConnected) return;
// Add user message to chat
this.addMessage(message, 'user');
// Clear input and disable send button
this.messageInput.value = '';
this.messageInput.style.height = 'auto';
this.toggleSendButton();
// Show typing indicator
this.showTypingIndicator();
try {
// Create abort controller for timeout (20 minutes to match backend)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 1200000); // 20 minutes
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
conversation_id: this.conversationId
}),
signal: controller.signal,
// Add these to help Firefox
cache: 'no-cache',
mode: 'cors',
credentials: 'same-origin'
});
// Clear timeout if request completes successfully
clearTimeout(timeoutId);
const data = await response.json();
if (data.success) {
// Store conversation ID for future messages
this.conversationId = data.conversation_id;
// Update ticket integration with conversation ID
if (typeof updateConversationId === 'function') {
updateConversationId(this.conversationId);
}
// Add assistant response
this.addMessage(data.response, 'assistant', {
responseTime: data.response_time_ms,
model: data.model_used
});
} else {
// Display the actual error message from the API
const errorMessage = data.error || 'Sorry, I encountered an error. Please try again or contact our support team.';
this.addMessage(errorMessage, 'assistant', { isError: true });
}
} catch (error) {
console.error('Error sending message:', error);
this.addMessage('Connection error. Please check your internet connection and try again.', 'assistant', { isError: true });
} finally {
this.hideTypingIndicator();
}
}
addMessage(content, role, metadata = {}) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}-message`;
const timestamp = new Date().toLocaleTimeString();
let messageHTML = '';
if (role === 'user') {
messageHTML = `
<div class="message-content">
<div class="message-text">${this.escapeHtml(content)}</div>
<div class="message-time">${timestamp}</div>
</div>
<div class="message-avatar">👤</div>
`;
} else {
const errorClass = metadata.isError ? ' error' : '';
const responseTimeText = metadata.responseTime ? ` (${metadata.responseTime}ms)` : '';
messageHTML = `
<div class="message-avatar">🤖</div>
<div class="message-content${errorClass}">
<div class="message-text">${this.escapeHtml(content)}</div>
<div class="message-time">${timestamp}${responseTimeText}</div>
</div>
`;
}
messageDiv.innerHTML = messageHTML;
if (this.chatMessages) {
this.chatMessages.appendChild(messageDiv);
this.scrollToBottom();
}
}
showTypingIndicator() {
if (this.typingIndicator) {
this.typingIndicator.style.display = 'flex';
this.scrollToBottom();
}
}
hideTypingIndicator() {
if (this.typingIndicator) {
this.typingIndicator.style.display = 'none';
}
}
scrollToBottom() {
if (this.chatMessages) {
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
}
}
async clearConversation() {
if (!this.conversationId) {
// Just clear the UI if no conversation ID
this.clearChatUI();
return;
}
if (confirm('Are you sure you want to clear this conversation?')) {
try {
const response = await fetch(`/api/conversation/${this.conversationId}/clear`, {
method: 'POST'
});
if (response.ok) {
this.clearChatUI();
this.conversationId = null;
} else {
alert('Failed to clear conversation. Please try again.');
}
} catch (error) {
console.error('Error clearing conversation:', error);
alert('Error clearing conversation. Please try again.');
}
}
}
async endConversation() {
if (!this.conversationId) {
alert('No active conversation to end.');
return;
}
if (confirm('End this conversation and save a summary to any mentioned tickets?')) {
try {
const response = await fetch('/api/conversation/end', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
conversation_id: this.conversationId
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
alert(result.message || 'Conversation ended successfully!');
this.clearChatUI();
this.conversationId = null;
} else {
alert(result.message || 'Failed to end conversation properly.');
}
} else {
alert('Failed to end conversation. Please try again.');
}
} catch (error) {
console.error('Error ending conversation:', error);
alert('Error ending conversation. Please try again.');
}
}
}
clearChatUI() {
if (this.chatMessages) {
// Keep only the welcome message
const welcomeMessage = this.chatMessages.querySelector('.welcome-message');
this.chatMessages.innerHTML = '';
if (welcomeMessage) {
this.chatMessages.appendChild(welcomeMessage);
}
}
}
async loadConversationHistory() {
// Only load history if user is authenticated
try {
const response = await fetch('/api/user');
if (response.ok) {
const userData = await response.json();
if (userData.success) {
// User is logged in, could load recent conversations here
console.log('User authenticated:', userData.user.name);
}
}
} catch (error) {
// User not authenticated, that's fine
console.log('User not authenticated');
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Initialize chat interface when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
if (document.getElementById('chat-messages')) {
new ChatInterface();
}
});