milwright commited on
Commit
6f3b96e
Β·
1 Parent(s): 793a92d

fix: use complete app.js with full UI logic

Browse files

- Replace minimal app.js with full implementation from main
- Includes startNewGame, displayRound, event handlers, etc.
- Actually starts the game after initialization
- Properly manages UI state and user interactions

Files changed (1) hide show
  1. src/app.js +309 -34
src/app.js CHANGED
@@ -1,44 +1,319 @@
1
  // Main application entry point
2
- import ClozeGameEngine from './clozeGameEngine.js';
3
- import bookDataService from './bookDataService.js';
4
- import { AIService } from './aiService.js';
5
- import ChatInterface from './chatInterface.js';
6
- import ConversationManager from './conversationManager.js';
7
-
8
- class ClozeReaderApp {
9
- constructor() {
10
- this.bookDataService = bookDataService; // Already instantiated
11
- this.aiService = new AIService();
12
- this.gameEngine = new ClozeGameEngine();
13
- this.chatInterface = new ChatInterface(this.gameEngine);
14
- this.conversationManager = new ConversationManager(this.aiService);
15
-
16
- this.init();
17
- }
18
-
19
- async init() {
20
- try {
21
- // Start the game directly - bookDataService is already initialized
22
- await this.gameEngine.initialize();
23
-
24
- // Chat interface is already initialized in constructor
25
-
26
- console.log('Cloze Reader application initialized successfully');
27
- } catch (error) {
28
- console.error('Failed to initialize application:', error);
29
- this.showError('Failed to load the application. Please refresh the page.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
 
32
 
33
- showError(message) {
34
- const loadingElement = document.getElementById('loading');
35
- if (loadingElement) {
36
- loadingElement.innerHTML = `<p class="text-red-600">${message}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
- // Initialize the application when DOM is loaded
42
  document.addEventListener('DOMContentLoaded', () => {
43
- new ClozeReaderApp();
 
 
 
 
 
 
 
44
  });
 
1
  // Main application entry point
2
+ import ClozeGame from './clozeGameEngine.js';
3
+ import ChatUI from './chatInterface.js';
4
+
5
+ class App {
6
+ constructor() {
7
+ this.game = new ClozeGame();
8
+ this.chatUI = new ChatUI(this.game);
9
+ this.elements = {
10
+ loading: document.getElementById('loading'),
11
+ gameArea: document.getElementById('game-area'),
12
+ bookInfo: document.getElementById('book-info'),
13
+ roundInfo: document.getElementById('round-info'),
14
+ contextualization: document.getElementById('contextualization'),
15
+ passageContent: document.getElementById('passage-content'),
16
+ hintsSection: document.getElementById('hints-section'),
17
+ hintsList: document.getElementById('hints-list'),
18
+ submitBtn: document.getElementById('submit-btn'),
19
+ nextBtn: document.getElementById('next-btn'),
20
+ hintBtn: document.getElementById('hint-btn'),
21
+ result: document.getElementById('result')
22
+ };
23
+
24
+ this.currentResults = null;
25
+ this.setupEventListeners();
26
+ }
27
+
28
+ async initialize() {
29
+ try {
30
+ this.showLoading(true);
31
+ await this.game.initialize();
32
+ await this.startNewGame();
33
+ this.showLoading(false);
34
+ } catch (error) {
35
+ console.error('Failed to initialize app:', error);
36
+ this.showError('Failed to load the game. Please refresh and try again.');
37
+ }
38
+ }
39
+
40
+ setupEventListeners() {
41
+ this.elements.submitBtn.addEventListener('click', () => this.handleSubmit());
42
+ this.elements.nextBtn.addEventListener('click', () => this.handleNext());
43
+ this.elements.hintBtn.addEventListener('click', () => this.toggleHints());
44
+
45
+ // Allow Enter key to submit when focused on an input
46
+ document.addEventListener('keydown', (e) => {
47
+ if (e.key === 'Enter' && e.target.classList.contains('cloze-input')) {
48
+ this.handleSubmit();
49
+ }
50
+ });
51
+ }
52
+
53
+ async startNewGame() {
54
+ try {
55
+ const roundData = await this.game.startNewRound();
56
+ this.displayRound(roundData);
57
+ this.resetUI();
58
+ } catch (error) {
59
+ console.error('Error starting new game:', error);
60
+ this.showError('Could not load a new passage. Please try again.');
61
+ }
62
+ }
63
+
64
+ displayRound(roundData) {
65
+ // Show book information
66
+ this.elements.bookInfo.innerHTML = `
67
+ <strong>${roundData.title}</strong> by ${roundData.author}
68
+ `;
69
+
70
+ // Show level information without round number
71
+ const blanksCount = roundData.blanks.length;
72
+ const difficultyText = blanksCount === 1 ? 'Easy' : blanksCount === 2 ? 'Medium' : 'Hard';
73
+ this.elements.roundInfo.innerHTML = `Level ${this.game.currentLevel} β€’ ${blanksCount} blank${blanksCount > 1 ? 's' : ''} β€’ ${difficultyText}`;
74
+
75
+ // Show contextualization from AI agent
76
+ this.elements.contextualization.innerHTML = `
77
+ <div class="flex items-start gap-2">
78
+ <span class="text-blue-600">πŸ“š</span>
79
+ <span>${roundData.contextualization || 'Loading context...'}</span>
80
+ </div>
81
+ `;
82
+
83
+ // Render the cloze text with input fields and chat buttons
84
+ const clozeHtml = this.game.renderClozeTextWithChat();
85
+ this.elements.passageContent.innerHTML = `<p>${clozeHtml}</p>`;
86
+
87
+ // Store hints for later display
88
+ this.currentHints = roundData.hints || [];
89
+ this.populateHints();
90
+
91
+ // Hide hints initially
92
+ this.elements.hintsSection.style.display = 'none';
93
+
94
+ // Set up input field listeners
95
+ this.setupInputListeners();
96
+
97
+ // Set up chat buttons
98
+ this.chatUI.setupChatButtons();
99
+ }
100
+
101
+ setupInputListeners() {
102
+ const inputs = this.elements.passageContent.querySelectorAll('.cloze-input');
103
+
104
+ inputs.forEach((input, index) => {
105
+ input.addEventListener('input', () => {
106
+ // Remove any previous styling
107
+ input.classList.remove('correct', 'incorrect');
108
+ this.updateSubmitButton();
109
+ });
110
+
111
+ input.addEventListener('keydown', (e) => {
112
+ if (e.key === 'Enter') {
113
+ e.preventDefault();
114
+
115
+ // Move to next input or submit if last
116
+ const nextInput = inputs[index + 1];
117
+ if (nextInput) {
118
+ nextInput.focus();
119
+ } else {
120
+ this.handleSubmit();
121
+ }
122
+ }
123
+ });
124
+ });
125
+
126
+ // Focus first input
127
+ if (inputs.length > 0) {
128
+ inputs[0].focus();
129
+ }
130
+ }
131
+
132
+ updateSubmitButton() {
133
+ const inputs = this.elements.passageContent.querySelectorAll('.cloze-input');
134
+ const allFilled = Array.from(inputs).every(input => input.value.trim() !== '');
135
+ this.elements.submitBtn.disabled = !allFilled;
136
+ }
137
+
138
+ handleSubmit() {
139
+ const inputs = this.elements.passageContent.querySelectorAll('.cloze-input');
140
+ const answers = Array.from(inputs).map(input => input.value.trim());
141
+
142
+ // Check if all fields are filled
143
+ if (answers.some(answer => answer === '')) {
144
+ alert('Please fill in all blanks before submitting.');
145
+ return;
146
+ }
147
+
148
+ // Submit answers and get results
149
+ this.currentResults = this.game.submitAnswers(answers);
150
+ this.displayResults(this.currentResults);
151
+ this.highlightAnswers(this.currentResults.results);
152
+ }
153
+
154
+ displayResults(results) {
155
+ let message = `Score: ${results.correct}/${results.total} (${results.percentage}%)`;
156
+
157
+ // Only show "Required" information at Level 3 and above
158
+ if (this.game.currentLevel >= 3) {
159
+ message += ` - Required: ${results.requiredCorrect}/${results.total}`;
160
+ }
161
+
162
+ if (results.passed) {
163
+ message += ` - Excellent! Advancing to Level ${this.game.currentLevel + 1}! πŸŽ‰`;
164
+ this.elements.result.className = 'mt-4 text-center font-semibold text-green-600';
165
+ } else {
166
+ if (this.game.currentLevel >= 3) {
167
+ message += ` - Need ${results.requiredCorrect} correct to advance. Keep practicing! πŸ’ͺ`;
168
+ } else {
169
+ message += ` - Keep practicing! πŸ’ͺ`;
170
+ }
171
+ this.elements.result.className = 'mt-4 text-center font-semibold text-red-600';
172
+ }
173
+
174
+ this.elements.result.textContent = message;
175
+
176
+ // Always reveal answers at the end of each round
177
+ this.revealAnswersInPlace(results.results);
178
+
179
+ // Show next button and hide submit button
180
+ this.elements.submitBtn.style.display = 'none';
181
+ this.elements.nextBtn.classList.remove('hidden');
182
+ }
183
+
184
+ highlightAnswers(results) {
185
+ const inputs = this.elements.passageContent.querySelectorAll('.cloze-input');
186
+
187
+ results.forEach((result, index) => {
188
+ const input = inputs[index];
189
+ if (input) {
190
+ if (result.isCorrect) {
191
+ input.classList.add('correct');
192
+ } else {
193
+ input.classList.add('incorrect');
194
+ // Show correct answer as placeholder or title
195
+ input.title = `Correct answer: ${result.correctAnswer}`;
196
  }
197
+ input.disabled = true;
198
+ }
199
+ });
200
+ }
201
+
202
+ async handleNext() {
203
+ try {
204
+ // Show loading immediately with specific message
205
+ this.showLoading(true, 'Loading next passage...');
206
+
207
+ // Clear chat history when starting new round
208
+ this.chatUI.clearChatHistory();
209
+
210
+ const roundData = await this.game.nextRound();
211
+ this.displayRound(roundData);
212
+ this.resetUI();
213
+ this.showLoading(false);
214
+ } catch (error) {
215
+ console.error('Error loading next round:', error);
216
+ this.showError('Could not load next passage. Please try again.');
217
  }
218
+ }
219
 
220
+ // Reveal correct answers immediately after submission
221
+ revealAnswersInPlace(results) {
222
+ const inputs = this.elements.passageContent.querySelectorAll('.cloze-input');
223
+
224
+ results.forEach((result, index) => {
225
+ const input = inputs[index];
226
+ if (input) {
227
+ if (result.isCorrect) {
228
+ input.classList.add('correct');
229
+ input.style.backgroundColor = '#dcfce7'; // Light green
230
+ input.style.borderColor = '#16a34a'; // Green border
231
+ } else {
232
+ input.classList.add('incorrect');
233
+ input.style.backgroundColor = '#fef2f2'; // Light red
234
+ input.style.borderColor = '#dc2626'; // Red border
235
+
236
+ // Show correct answer below the input
237
+ const correctAnswerSpan = document.createElement('span');
238
+ correctAnswerSpan.className = 'text-sm text-green-600 font-semibold ml-2';
239
+ correctAnswerSpan.textContent = `βœ“ ${result.correctAnswer}`;
240
+ input.parentNode.appendChild(correctAnswerSpan);
241
  }
242
+ input.disabled = true;
243
+ }
244
+ });
245
+ }
246
+
247
+ populateHints() {
248
+ if (!this.currentHints || this.currentHints.length === 0) {
249
+ this.elements.hintsList.innerHTML = '<div class="text-yellow-600">No hints available for this passage.</div>';
250
+ return;
251
+ }
252
+
253
+ const hintsHtml = this.currentHints.map((hintData, index) =>
254
+ `<div class="flex items-start gap-2">
255
+ <span class="font-semibold text-yellow-800">${index + 1}.</span>
256
+ <span>${hintData.hint}</span>
257
+ </div>`
258
+ ).join('');
259
+
260
+ this.elements.hintsList.innerHTML = hintsHtml;
261
+ }
262
+
263
+ toggleHints() {
264
+ const isHidden = this.elements.hintsSection.style.display === 'none';
265
+ this.elements.hintsSection.style.display = isHidden ? 'block' : 'none';
266
+ this.elements.hintBtn.textContent = isHidden ? 'Hide Hints' : 'Show Hints';
267
+ }
268
+
269
+ resetUI() {
270
+ this.elements.result.textContent = '';
271
+ this.elements.submitBtn.style.display = 'inline-block';
272
+ this.elements.submitBtn.disabled = true;
273
+ this.elements.nextBtn.classList.add('hidden');
274
+ this.elements.hintsSection.style.display = 'none';
275
+ this.elements.hintBtn.textContent = 'Show Hints';
276
+ this.currentResults = null;
277
+ this.currentHints = [];
278
+ }
279
+
280
+ showLoading(show, message = 'Loading passages...') {
281
+ if (show) {
282
+ this.elements.loading.innerHTML = `
283
+ <div class="text-center py-8">
284
+ <p class="text-lg loading-text">${message}</p>
285
+ </div>
286
+ `;
287
+ this.elements.loading.classList.remove('hidden');
288
+ this.elements.gameArea.classList.add('hidden');
289
+ } else {
290
+ this.elements.loading.classList.add('hidden');
291
+ this.elements.gameArea.classList.remove('hidden');
292
  }
293
+ }
294
+
295
+ showError(message) {
296
+ this.elements.loading.innerHTML = `
297
+ <div class="text-center py-8">
298
+ <p class="text-lg text-red-600 mb-4">${message}</p>
299
+ <button onclick="location.reload()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
300
+ Reload
301
+ </button>
302
+ </div>
303
+ `;
304
+ this.elements.loading.classList.remove('hidden');
305
+ this.elements.gameArea.classList.add('hidden');
306
+ }
307
  }
308
 
309
+ // Initialize the app when DOM is loaded
310
  document.addEventListener('DOMContentLoaded', () => {
311
+ const app = new App();
312
+ app.initialize();
313
+
314
+ // Expose API key setter for browser console
315
+ window.setOpenRouterKey = (key) => {
316
+ app.game.chatService.aiService.setApiKey(key);
317
+ console.log('OpenRouter API key updated');
318
+ };
319
  });