// Chat UI components for contextual hints
class ChatUI {
constructor(gameLogic) {
this.game = gameLogic;
this.activeChatBlank = null;
this.chatModal = null;
this.isOpen = false;
this.messageHistory = new Map(); // blankId -> array of messages for persistent history
this.setupChatModal();
}
// Create and setup chat modal
setupChatModal() {
// Create modal HTML
const modalHTML = `
Chat about Word #1
Ask me anything about this word! I can help with meaning, context, grammar, or give you hints.
`;
// Insert modal into page
document.body.insertAdjacentHTML('beforeend', modalHTML);
this.chatModal = document.getElementById('chat-modal');
this.setupEventListeners();
}
// Setup event listeners for chat modal
setupEventListeners() {
const closeBtn = document.getElementById('chat-close');
// Close modal
closeBtn.addEventListener('click', () => this.closeChat());
this.chatModal.addEventListener('click', (e) => {
if (e.target === this.chatModal) this.closeChat();
});
// ESC key to close
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) this.closeChat();
});
}
// Open chat for specific blank
async openChat(blankIndex) {
this.activeChatBlank = blankIndex;
this.isOpen = true;
// Update title
const title = document.getElementById('chat-title');
title.textContent = `Help with Word #${blankIndex + 1}`;
// Restore previous messages or show intro
this.restoreMessages(blankIndex);
// Load question buttons
this.loadQuestionButtons();
// Show modal
this.chatModal.classList.remove('hidden');
}
// Close chat modal
closeChat() {
this.isOpen = false;
this.chatModal.classList.add('hidden');
this.activeChatBlank = null;
}
// Clear messages and show intro
clearMessages() {
const messagesContainer = document.getElementById('chat-messages');
messagesContainer.innerHTML = `
Choose a question below to get help with this word.
`;
}
// Restore messages for a specific blank or show intro
restoreMessages(blankIndex) {
const messagesContainer = document.getElementById('chat-messages');
const blankId = `blank_${blankIndex}`;
const history = this.messageHistory.get(blankId);
if (history && history.length > 0) {
// Restore previous messages
messagesContainer.innerHTML = '';
history.forEach(msg => {
this.displayMessage(msg.sender, msg.content, msg.isUser);
});
} else {
// Show intro for new conversation
this.clearMessages();
}
}
// Display a message without storing it (used for restoration)
displayMessage(sender, content, isUser) {
const messagesContainer = document.getElementById('chat-messages');
const alignment = isUser ? 'flex justify-end' : 'flex justify-start';
const messageClass = isUser
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-900';
const displaySender = isUser ? 'You' : sender;
const messageHTML = `
${displaySender}
${this.escapeHtml(content)}
`;
messagesContainer.insertAdjacentHTML('beforeend', messageHTML);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Clear all chat history (called when round ends)
clearChatHistory() {
this.messageHistory.clear();
}
// Load question dropdown with disabled state for used questions
loadQuestionButtons() {
const dropdown = document.getElementById('question-dropdown');
const questions = this.game.getSuggestedQuestionsForBlank(this.activeChatBlank);
// Clear existing content
dropdown.innerHTML = '';
// Build dropdown options
questions.forEach(question => {
const isDisabled = question.used;
const optionText = isDisabled ? `${question.text} ✓` : question.text;
// Add all options but mark used ones as disabled
const option = document.createElement('option');
option.value = isDisabled ? '' : question.type;
option.textContent = optionText;
option.disabled = isDisabled;
option.style.color = isDisabled ? '#9CA3AF' : '#111827';
dropdown.appendChild(option);
});
// Add change listener to dropdown
dropdown.addEventListener('change', (e) => {
if (e.target.value) {
this.askQuestion(e.target.value);
e.target.value = ''; // Reset dropdown
}
});
}
// Ask a specific question
async askQuestion(questionType) {
if (this.activeChatBlank === null) return;
// Get current user input for the blank
const currentInput = this.getCurrentBlankInput();
// Get the actual question text from the button that was clicked
const questions = this.game.getSuggestedQuestionsForBlank(this.activeChatBlank);
const selectedQuestion = questions.find(q => q.type === questionType);
const questionText = selectedQuestion ? selectedQuestion.text : this.getQuestionText(questionType);
// Show question and loading
this.addMessageToChat('You', questionText, true);
this.showTypingIndicator();
try {
// Send to chat service with question type
const response = await this.game.askQuestionAboutBlank(
this.activeChatBlank,
questionType,
currentInput
);
this.hideTypingIndicator();
if (response.success) {
// Make sure we're displaying the response string, not the object
const responseText = typeof response.response === 'string'
? response.response
: response.response.response || 'Sorry, I had trouble with that question.';
this.addMessageToChat('Cluemaster', responseText, false);
// Refresh question buttons to show the used question as disabled
this.loadQuestionButtons();
} else {
this.addMessageToChat('Cluemaster', response.message || 'Sorry, I had trouble with that question.', false);
}
} catch (error) {
this.hideTypingIndicator();
console.error('Chat error:', error);
this.addMessageToChat('Cluemaster', 'Sorry, I encountered an error. Please try again.', false);
}
}
// Get question text for display
getQuestionText(questionType) {
const questions = {
'grammar': 'What type of word is this?',
'meaning': 'What does this word mean?',
'context': 'Why does this word fit here?',
'clue': 'Give me a clue'
};
return questions[questionType] || questions['clue'];
}
// Get current input for the active blank
getCurrentBlankInput() {
const input = document.querySelector(`input[data-blank-index="${this.activeChatBlank}"]`);
return input ? input.value.trim() : '';
}
// Add message to chat display and store in history
addMessageToChat(sender, content, isUser) {
// Store message in history for current blank
if (this.activeChatBlank !== null) {
const blankId = `blank_${this.activeChatBlank}`;
if (!this.messageHistory.has(blankId)) {
this.messageHistory.set(blankId, []);
}
// Change "Tutor" to "Cluemaster" for display and storage
const displaySender = sender === 'Tutor' ? 'Cluemaster' : sender;
this.messageHistory.get(blankId).push({
sender: displaySender,
content: content,
isUser: isUser,
timestamp: Date.now()
});
}
// Display the message
this.displayMessage(sender === 'Tutor' ? 'Cluemaster' : sender, content, isUser);
}
// Show typing indicator
showTypingIndicator() {
const messagesContainer = document.getElementById('chat-messages');
const typingHTML = `