• Joe Chen's avatar
    ss · 3fc5e3e4
    Joe Chen authored
    3fc5e3e4
script.js 8.79 KB
// DOM Elements
const chatBox = document.getElementById('groqChatBox');
const chatForm = document.getElementById('groqChatForm');
const userInput = document.getElementById('groqUserInput');
const sendButton = document.getElementById('groqSendButton');

// Generate a random session ID for this chat instance
const sessionId = 'session_' + Math.random().toString(36).substring(2, 15);

// State
let isWaitingForResponse = false;
let retryCount = 0;
const MAX_RETRIES = 3;
const RETRY_DELAY = 6000;

// Initialize chat
document.addEventListener('DOMContentLoaded', () => {
    // Set up UI
    setupUI();
    
    // Auto-focus the input field
    userInput.focus();
    
    // Scroll to bottom of chat
    scrollToBottom();
    
    // Update timestamp on initial message
    updateInitialMessageTime();
});

function setupUI() {
    // Disable button initially if input is empty
    sendButton.disabled = userInput.value.trim() === '';
    
    // Set up event listeners for Enter key
    userInput.addEventListener('keydown', handleEnterKey);
    
    // Set up input listener for button state
    userInput.addEventListener('input', updateButtonState);
    
    // Handle window resize for mobile
    window.addEventListener('resize', scrollToBottom);
}

function updateInitialMessageTime() {
    const initialTimeElement = document.querySelector('.message.system .message-time');
    if (initialTimeElement) {
        const now = new Date();
        initialTimeElement.textContent = formatTime(now);
    }
}

// Handle Enter key press
function handleEnterKey(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        if (!isWaitingForResponse && userInput.value.trim() !== '') {
            chatForm.dispatchEvent(new Event('submit'));
        }
    }
}

// Update button state based on input
function updateButtonState() {
    sendButton.disabled = userInput.value.trim() === '' || isWaitingForResponse;
}

// Handle form submission
chatForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const userMessage = userInput.value.trim();
    
    // Don't do anything if the message is empty or we're waiting for a response
    if (!userMessage || isWaitingForResponse) return;
    
    // Add user message to chat
    addMessageToChat('user', userMessage);
    
    // Clear input field
    userInput.value = '';
    updateButtonState();
    
    // Show typing indicator
    showTypingIndicator();
    
    // Set waiting state
    isWaitingForResponse = true;
    
    try {
        // Send message to backend and get response
        const response = await sendMessageToBackend(userMessage);
        
        // Hide typing indicator
        hideTypingIndicator();
        
        // Add assistant's response to chat
        if (response && response.answer) {
            addMessageToChat('assistant', response.answer);
            // Reset retry count on successful response
            retryCount = 0;
        } else {
            throw new Error('Invalid response format from server');
        }
    } catch (error) {
        console.error('Error communicating with backend:', error);
        
        // Hide typing indicator
        hideTypingIndicator();
        
        // Handle retry logic
        if (retryCount < MAX_RETRIES) {
            retryCount++;
            addMessageToChat('assistant', "I'm having trouble connecting to the server. Retrying (" + retryCount + "/" + MAX_RETRIES + ")...");
            
            // Wait and retry
            setTimeout(() => {
                isWaitingForResponse = false;
                retryQuery(userMessage);
            }, RETRY_DELAY);
            return;
        }
        
        // Add error message after max retries
        addMessageToChat('assistant', "Sorry, I'm having trouble connecting to the server. Please try again later or refresh the page.");
    }
    
    // Reset waiting state
    isWaitingForResponse = false;
    
    // Focus input again
    userInput.focus();
});

// Function to retry a failed query
async function retryQuery(message) {
    // Show typing indicator again
    showTypingIndicator();
    
    // Set waiting state
    isWaitingForResponse = true;
    
    try {
        // Retry the API call
        const response = await sendMessageToBackend(message);
        
        // Hide typing indicator
        hideTypingIndicator();
        
        // Add assistant's response to chat if successful
        if (response && response.answer) {
            addMessageToChat('assistant', response.answer);
            retryCount = 0;
        } else {
            throw new Error('Invalid response format from server');
        }
    } catch (error) {
        console.error('Retry failed:', error);
        
        // Hide typing indicator
        hideTypingIndicator();
        
        // Handle continued retry logic
        if (retryCount < MAX_RETRIES) {
            retryCount++;
            addMessageToChat('assistant', "Still having trouble. Retrying (" + retryCount + "/" + MAX_RETRIES + ")...");
            
            // Wait and retry again
            setTimeout(() => {
                isWaitingForResponse = false;
                retryQuery(message);
            }, RETRY_DELAY);
            return;
        }
        
        // Give up after max retries
        addMessageToChat('assistant', "I apologize, but our server seems to be unavailable at the moment. Please try again later.");
    }
    
    // Reset waiting state
    isWaitingForResponse = false;
}

// Function to send message to backend
async function sendMessageToBackend(message) {
    // Create controller for timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
    
    try {
        const response = await fetch('/api/ask', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                session_id: sessionId,
                question: message,
                use_groq: true // Always use Groq
            }),
            signal: controller.signal
        });
        
        clearTimeout(timeoutId); // Clear the timeout
        
        if (!response.ok) {
            throw new Error(`Server responded with status: ${response.status}`);
        }
        
        return await response.json();
    } catch (error) {
        clearTimeout(timeoutId); // Clear the timeout in case of error
        throw error; // Re-throw for handling by caller
    }
}

// Function to add a message to the chat
function addMessageToChat(role, content) {
    // Create message container
    const messageDiv = document.createElement('div');
    messageDiv.classList.add('message', role);
    
    // Create message content
    const contentDiv = document.createElement('div');
    contentDiv.classList.add('message-content');
    
    // Add content paragraphs (handling potential line breaks)
    const paragraphs = content.split('\n').filter(p => p.trim() !== '');
    paragraphs.forEach(paragraph => {
        const p = document.createElement('p');
        p.textContent = paragraph;
        contentDiv.appendChild(p);
    });
    
    // Add time
    const timeDiv = document.createElement('div');
    timeDiv.classList.add('message-time');
    timeDiv.textContent = formatTime(new Date());
    
    // Assemble message
    messageDiv.appendChild(contentDiv);
    messageDiv.appendChild(timeDiv);
    
    // Add to chat
    chatBox.appendChild(messageDiv);
    
    // Scroll to bottom
    scrollToBottom();
}

// Format time as HH:MM
function formatTime(date) {
    const hours = date.getHours().toString().padStart(2, '0');
    const minutes = date.getMinutes().toString().padStart(2, '0');
    return `${hours}:${minutes}`;
}

// Show typing indicator
function showTypingIndicator() {
    // Create typing indicator if it doesn't exist
    let typingIndicator = chatBox.querySelector('.typing-indicator');
    
    if (!typingIndicator) {
        typingIndicator = document.createElement('div');
        typingIndicator.classList.add('message', 'assistant', 'typing-indicator');
        
        const contentDiv = document.createElement('div');
        contentDiv.classList.add('message-content');
        
        const p = document.createElement('p');
        p.innerHTML = '<span class="dot"></span><span class="dot"></span><span class="dot"></span>';
        
        contentDiv.appendChild(p);
        typingIndicator.appendChild(contentDiv);
        
        chatBox.appendChild(typingIndicator);
        scrollToBottom();
    }
}

// Hide typing indicator
function hideTypingIndicator() {
    const typingIndicator = chatBox.querySelector('.typing-indicator');
    
    if (typingIndicator) {
        chatBox.removeChild(typingIndicator);
    }
}

// Scroll to bottom of chat
function scrollToBottom() {
    chatBox.scrollTop = chatBox.scrollHeight;
}