File size: 11,215 Bytes
5dd5427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ca518f
5dd5427
3ca518f
83e8132
b451c6e
 
5dd5427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ca518f
5dd5427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ca518f
5dd5427
b451c6e
5dd5427
 
b451c6e
 
 
3ca518f
5dd5427
 
3ca518f
b451c6e
3ca518f
 
 
 
 
 
 
5dd5427
b451c6e
3ca518f
b451c6e
 
 
 
 
 
5dd5427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// 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 = `
      <div id="chat-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
        <div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 max-h-[80vh] flex flex-col">
          <!-- Header -->
          <div class="flex items-center justify-between p-4 border-b">
            <h3 id="chat-title" class="text-lg font-semibold text-gray-900">
              Chat about Word #1
            </h3>
            <button id="chat-close" class="text-gray-400 hover:text-gray-600">
              <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
              </svg>
            </button>
          </div>
          
          <!-- Chat messages area -->
          <div id="chat-messages" class="flex-1 overflow-y-auto p-4 min-h-[200px] max-h-[400px]">
            <div class="text-center text-gray-500 text-sm">
              Ask me anything about this word! I can help with meaning, context, grammar, or give you hints.
            </div>
          </div>
          
          <!-- Suggested questions -->
          <div id="suggested-questions" class="px-4 py-2 border-t border-gray-100">
            <div id="suggestion-buttons" class="flex flex-wrap gap-1">
              <!-- Suggestion buttons will be inserted here -->
            </div>
          </div>
          
          <!-- Question dropdown area -->
          <div class="p-4 border-t">
            <!-- Dropdown for all devices -->
            <select id="question-dropdown" class="w-full p-2 border rounded mb-4">
              <option value="">Select a question...</option>
            </select>
          </div>
        </div>
      </div>
    `;

    // 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 = `
      <div class="text-center text-gray-500 text-sm mb-4">
        Choose a question below to get help with this word.
      </div>
    `;
  }

  // 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 = `
      <div class="mb-3 ${alignment}">
        <div class="${messageClass} rounded-lg px-3 py-2 max-w-[80%]">
          <div class="text-xs font-medium mb-1">${displaySender}</div>
          <div class="text-sm">${this.escapeHtml(content)}</div>
        </div>
      </div>
    `;
    
    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 = '<option value="">Select a question...</option>';
    
    // 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 = `
      <div id="typing-indicator" class="mb-3 mr-auto max-w-[80%]">
        <div class="bg-gray-100 text-gray-900 rounded-lg px-3 py-2">
          <div class="text-xs font-medium mb-1">Cluemaster</div>
          <div class="text-sm">
            <span class="typing-dots">
              <span>.</span><span>.</span><span>.</span>
            </span>
          </div>
        </div>
      </div>
    `;
    
    messagesContainer.insertAdjacentHTML('beforeend', typingHTML);
    messagesContainer.scrollTop = messagesContainer.scrollHeight;
  }

  // Hide typing indicator
  hideTypingIndicator() {
    const indicator = document.getElementById('typing-indicator');
    if (indicator) indicator.remove();
  }

  // Escape HTML to prevent XSS
  escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

  // Setup chat buttons for blanks
  setupChatButtons() {
    // Remove existing listeners
    document.querySelectorAll('.chat-button').forEach(btn => {
      btn.replaceWith(btn.cloneNode(true));
    });
    
    // Add new listeners
    document.querySelectorAll('.chat-button').forEach(btn => {
      btn.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const blankIndex = parseInt(btn.dataset.blankIndex);
        this.openChat(blankIndex);
      });
    });
  }
}

export default ChatUI;