Spaces:
Running
Running
feat: implement two-passage system to avoid OpenRouter rate limits
Browse files- Each round now loads two different passages from two books
- Added passage numbering (Passage 1/2, Passage 2/2)
- Modified nextPassage() method to handle second passage in same round
- Updated UI to show current passage number
- Added proper error handling for OpenRouter API errors
- This spreads out API calls to avoid rate limiting issues
- src/aiService.js +10 -4
- src/app.js +15 -5
- src/clozeGameEngine.js +62 -7
src/aiService.js
CHANGED
@@ -139,8 +139,11 @@ Passage: "${passage}"`
|
|
139 |
|
140 |
const data = await response.json();
|
141 |
|
142 |
-
//
|
143 |
-
|
|
|
|
|
|
|
144 |
|
145 |
// Check if response has expected structure
|
146 |
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
@@ -218,8 +221,11 @@ Passage: "${passage}"`
|
|
218 |
|
219 |
const data = await response.json();
|
220 |
|
221 |
-
//
|
222 |
-
|
|
|
|
|
|
|
223 |
|
224 |
// Check if response has expected structure
|
225 |
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
|
|
139 |
|
140 |
const data = await response.json();
|
141 |
|
142 |
+
// Check for OpenRouter error response
|
143 |
+
if (data.error) {
|
144 |
+
console.error('OpenRouter API error for word selection:', data.error);
|
145 |
+
throw new Error(`OpenRouter API error: ${data.error.message || JSON.stringify(data.error)}`);
|
146 |
+
}
|
147 |
|
148 |
// Check if response has expected structure
|
149 |
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
|
|
221 |
|
222 |
const data = await response.json();
|
223 |
|
224 |
+
// Check for OpenRouter error response
|
225 |
+
if (data.error) {
|
226 |
+
console.error('OpenRouter API error for contextualization:', data.error);
|
227 |
+
throw new Error(`OpenRouter API error: ${data.error.message || JSON.stringify(data.error)}`);
|
228 |
+
}
|
229 |
|
230 |
// Check if response has expected structure
|
231 |
if (!data.choices || !data.choices[0] || !data.choices[0].message || !data.choices[0].message.content) {
|
src/app.js
CHANGED
@@ -69,10 +69,11 @@ class App {
|
|
69 |
<strong>${roundData.title}</strong> by ${roundData.author}
|
70 |
`;
|
71 |
|
72 |
-
// Show level information
|
73 |
const blanksCount = roundData.blanks.length;
|
74 |
const difficultyText = blanksCount === 1 ? 'Easy' : blanksCount === 2 ? 'Medium' : 'Hard';
|
75 |
-
|
|
|
76 |
|
77 |
// Show contextualization from AI agent
|
78 |
this.elements.contextualization.innerHTML = `
|
@@ -206,15 +207,24 @@ class App {
|
|
206 |
// Show loading immediately with specific message
|
207 |
this.showLoading(true, 'Loading next passage...');
|
208 |
|
209 |
-
// Clear chat history when starting new round
|
210 |
this.chatUI.clearChatHistory();
|
211 |
|
212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
this.displayRound(roundData);
|
214 |
this.resetUI();
|
215 |
this.showLoading(false);
|
216 |
} catch (error) {
|
217 |
-
console.error('Error loading next
|
218 |
this.showError('Could not load next passage. Please try again.');
|
219 |
}
|
220 |
}
|
|
|
69 |
<strong>${roundData.title}</strong> by ${roundData.author}
|
70 |
`;
|
71 |
|
72 |
+
// Show level information with passage number
|
73 |
const blanksCount = roundData.blanks.length;
|
74 |
const difficultyText = blanksCount === 1 ? 'Easy' : blanksCount === 2 ? 'Medium' : 'Hard';
|
75 |
+
const passageInfo = roundData.passageNumber ? `Passage ${roundData.passageNumber}/${roundData.totalPassages} • ` : '';
|
76 |
+
this.elements.roundInfo.innerHTML = `Level ${this.game.currentLevel} • ${passageInfo}${blanksCount} blank${blanksCount > 1 ? 's' : ''} • ${difficultyText}`;
|
77 |
|
78 |
// Show contextualization from AI agent
|
79 |
this.elements.contextualization.innerHTML = `
|
|
|
207 |
// Show loading immediately with specific message
|
208 |
this.showLoading(true, 'Loading next passage...');
|
209 |
|
210 |
+
// Clear chat history when starting new passage/round
|
211 |
this.chatUI.clearChatHistory();
|
212 |
|
213 |
+
// Check if we should load next passage or next round
|
214 |
+
let roundData;
|
215 |
+
if (this.game.currentPassageIndex === 0) {
|
216 |
+
// Load second passage in current round
|
217 |
+
roundData = await this.game.nextPassage();
|
218 |
+
} else {
|
219 |
+
// Load next round (two new passages)
|
220 |
+
roundData = await this.game.nextRound();
|
221 |
+
}
|
222 |
+
|
223 |
this.displayRound(roundData);
|
224 |
this.resetUI();
|
225 |
this.showLoading(false);
|
226 |
} catch (error) {
|
227 |
+
console.error('Error loading next passage:', error);
|
228 |
this.showError('Could not load next passage. Please try again.');
|
229 |
}
|
230 |
}
|
src/clozeGameEngine.js
CHANGED
@@ -19,6 +19,11 @@ class ClozeGame {
|
|
19 |
this.hints = [];
|
20 |
this.chatService = new ChatService(aiService);
|
21 |
this.lastResults = null; // Store results for answer revelation
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
23 |
|
24 |
async initialize() {
|
@@ -33,14 +38,22 @@ class ClozeGame {
|
|
33 |
|
34 |
async startNewRound() {
|
35 |
try {
|
36 |
-
// Get
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
-
//
|
40 |
-
|
41 |
-
|
|
|
42 |
|
43 |
-
|
|
|
|
|
44 |
|
45 |
// Run AI calls in parallel for faster loading
|
46 |
const [clozeResult, contextualizationResult] = await Promise.all([
|
@@ -54,7 +67,9 @@ class ClozeGame {
|
|
54 |
text: this.clozeText,
|
55 |
blanks: this.blanks,
|
56 |
contextualization: this.contextualization,
|
57 |
-
hints: this.hints
|
|
|
|
|
58 |
};
|
59 |
} catch (error) {
|
60 |
console.error('Error starting new round:', error);
|
@@ -451,6 +466,46 @@ class ClozeGame {
|
|
451 |
}));
|
452 |
}
|
453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
nextRound() {
|
455 |
// Check if user passed the previous round
|
456 |
const passed = this.lastResults && this.lastResults.passed;
|
|
|
19 |
this.hints = [];
|
20 |
this.chatService = new ChatService(aiService);
|
21 |
this.lastResults = null; // Store results for answer revelation
|
22 |
+
|
23 |
+
// Two-passage system properties
|
24 |
+
this.currentBooks = []; // Array of two books per round
|
25 |
+
this.passages = []; // Array of two passages per round
|
26 |
+
this.currentPassageIndex = 0; // 0 for first passage, 1 for second
|
27 |
}
|
28 |
|
29 |
async initialize() {
|
|
|
38 |
|
39 |
async startNewRound() {
|
40 |
try {
|
41 |
+
// Get two random books for this round
|
42 |
+
const book1 = await bookDataService.getRandomBook();
|
43 |
+
const book2 = await bookDataService.getRandomBook();
|
44 |
+
|
45 |
+
// Extract passages from both books
|
46 |
+
const passage1 = this.extractCoherentPassage(book1.text);
|
47 |
+
const passage2 = this.extractCoherentPassage(book2.text);
|
48 |
|
49 |
+
// Store both books and passages
|
50 |
+
this.currentBooks = [book1, book2];
|
51 |
+
this.passages = [passage1.trim(), passage2.trim()];
|
52 |
+
this.currentPassageIndex = 0;
|
53 |
|
54 |
+
// Start with the first passage
|
55 |
+
this.currentBook = book1;
|
56 |
+
this.originalText = this.passages[0];
|
57 |
|
58 |
// Run AI calls in parallel for faster loading
|
59 |
const [clozeResult, contextualizationResult] = await Promise.all([
|
|
|
67 |
text: this.clozeText,
|
68 |
blanks: this.blanks,
|
69 |
contextualization: this.contextualization,
|
70 |
+
hints: this.hints,
|
71 |
+
passageNumber: 1,
|
72 |
+
totalPassages: 2
|
73 |
};
|
74 |
} catch (error) {
|
75 |
console.error('Error starting new round:', error);
|
|
|
466 |
}));
|
467 |
}
|
468 |
|
469 |
+
async nextPassage() {
|
470 |
+
try {
|
471 |
+
// Move to the second passage in the current round
|
472 |
+
if (this.currentPassageIndex === 0 && this.passages && this.passages.length > 1) {
|
473 |
+
this.currentPassageIndex = 1;
|
474 |
+
this.currentBook = this.currentBooks[1];
|
475 |
+
this.originalText = this.passages[1];
|
476 |
+
|
477 |
+
// Clear chat conversations for new passage
|
478 |
+
this.chatService.clearConversations();
|
479 |
+
|
480 |
+
// Clear last results
|
481 |
+
this.lastResults = null;
|
482 |
+
|
483 |
+
// Generate new cloze text and contextualization for second passage
|
484 |
+
const [clozeResult, contextualizationResult] = await Promise.all([
|
485 |
+
this.createClozeText(),
|
486 |
+
this.generateContextualization()
|
487 |
+
]);
|
488 |
+
|
489 |
+
return {
|
490 |
+
title: this.currentBook.title,
|
491 |
+
author: this.currentBook.author,
|
492 |
+
text: this.clozeText,
|
493 |
+
blanks: this.blanks,
|
494 |
+
contextualization: this.contextualization,
|
495 |
+
hints: this.hints,
|
496 |
+
passageNumber: 2,
|
497 |
+
totalPassages: 2
|
498 |
+
};
|
499 |
+
} else {
|
500 |
+
// If we're already on the second passage, move to next round
|
501 |
+
return this.nextRound();
|
502 |
+
}
|
503 |
+
} catch (error) {
|
504 |
+
console.error('Error loading next passage:', error);
|
505 |
+
throw error;
|
506 |
+
}
|
507 |
+
}
|
508 |
+
|
509 |
nextRound() {
|
510 |
// Check if user passed the previous round
|
511 |
const passed = this.lastResults && this.lastResults.passed;
|