milwright commited on
Commit
106bc8b
·
1 Parent(s): cca074a

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

Files changed (3) hide show
  1. src/aiService.js +10 -4
  2. src/app.js +15 -5
  3. src/clozeGameEngine.js +62 -7
src/aiService.js CHANGED
@@ -139,8 +139,11 @@ Passage: "${passage}"`
139
 
140
  const data = await response.json();
141
 
142
- // Debug: Log the actual API response
143
- console.log('Word selection API response:', data);
 
 
 
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
- // Debug: Log the actual API response
222
- console.log('Contextualization API response:', data);
 
 
 
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 without round number
73
  const blanksCount = roundData.blanks.length;
74
  const difficultyText = blanksCount === 1 ? 'Easy' : blanksCount === 2 ? 'Medium' : 'Hard';
75
- this.elements.roundInfo.innerHTML = `Level ${this.game.currentLevel} • ${blanksCount} blank${blanksCount > 1 ? 's' : ''} • ${difficultyText}`;
 
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
- const roundData = await this.game.nextRound();
 
 
 
 
 
 
 
 
 
213
  this.displayRound(roundData);
214
  this.resetUI();
215
  this.showLoading(false);
216
  } catch (error) {
217
- console.error('Error loading next round:', error);
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 a random book (now async with HF streaming)
37
- this.currentBook = await bookDataService.getRandomBook();
 
 
 
 
 
38
 
39
- // Extract a coherent passage avoiding fragmented text
40
- const fullText = this.currentBook.text;
41
- let passage = this.extractCoherentPassage(fullText);
 
42
 
43
- this.originalText = passage.trim();
 
 
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;