milwright commited on
Commit
440ff08
·
1 Parent(s): c21250b

fix level progression and optimize ai word selection

Browse files

- Fix level progression bug where advancement only considered second passage
- Add round-level result tracking for proper level advancement
- Improve AI prompts to prevent selection from first/last clauses
- Add JSON parsing robustness for trailing commas and malformed arrays
- Enhance word selection criteria to avoid ALL-CAPS and table contents
- Update difficulty scaling: 1-5 (1 blank), 6-10 (2 blanks), 11+ (3 blanks)
- Optimize batch processing with exact word count requirements

docs/ai-prompts-and-parameters.md CHANGED
@@ -6,6 +6,23 @@ This document outlines the different types of AI requests, prompts, and paramete
6
 
7
  The Cloze Reader uses OpenRouter's API with the `google/gemma-3-27b-it:free` model to power various AI-driven features. All requests use a consistent retry mechanism with exponential backoff (3 attempts, 0.5s initial delay).
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ## Request Types
10
 
11
  ### 1. Contextual Hint Generation
@@ -55,14 +72,18 @@ The Cloze Reader uses OpenRouter's API with the `google/gemma-3-27b-it:free` mod
55
  "messages": [
56
  {
57
  "role": "user",
58
- "content": "You are a cluemaster vocabulary selector for educational cloze exercises. Select exactly [COUNT] words from this passage for a cloze exercise.\n\nREQUIREMENTS:\n- Choose clear, properly-spelled words (no OCR errors like \"andsatires\")\n- Select meaningful nouns, verbs, or adjectives (4-12 letters)\n- Words must appear EXACTLY as written in the passage\n- Avoid: capitalized words, function words, archaic terms, proper nouns, technical jargon\n- Skip any words that look malformed or concatenated\n\nReturn ONLY a JSON array of the selected words.\n\nPassage: \"[PASSAGE_TEXT]\""
59
  }
60
  ],
61
  "max_tokens": 100,
62
- "temperature": 0.5
63
  }
64
  ```
65
 
 
 
 
 
66
  **Response Format:** JSON array of strings
67
  ```json
68
  ["word1", "word2", "word3"]
@@ -83,14 +104,18 @@ The Cloze Reader uses OpenRouter's API with the `google/gemma-3-27b-it:free` mod
83
  },
84
  {
85
  "role": "user",
86
- "content": "Process these two passages for cloze exercises:\n\nPASSAGE 1:\nTitle: \"[BOOK1_TITLE]\" by [BOOK1_AUTHOR]\nText: \"[PASSAGE1_TEXT]\"\nSelect [COUNT] words for blanks.\n\nPASSAGE 2:\nTitle: \"[BOOK2_TITLE]\" by [BOOK2_AUTHOR]\nText: \"[PASSAGE2_TEXT]\"\nSelect [COUNT] words for blanks.\n\nFor each passage return:\n- \"words\": array of selected words (exactly as they appear)\n- \"context\": one-sentence intro about the book/author\n\nReturn as JSON: {\"passage1\": {...}, \"passage2\": {...}}"
87
  }
88
  ],
89
- "max_tokens": 500,
90
  "temperature": 0.5
91
  }
92
  ```
93
 
 
 
 
 
94
  **Response Format:**
95
  ```json
96
  {
@@ -150,12 +175,12 @@ All requests include these headers:
150
  |---------|------------|-------------|-------------|
151
  | Hints | 50 | 0.6 | 3 attempts |
152
  | Word Selection | 100 | 0.3 | 3 attempts |
153
- | Batch Processing | 500 | 0.3 | 3 attempts |
154
- | Contextualization | 80 | 0.2 | 3 attempts |
155
 
156
  ### Temperature Guidelines
157
- - **0.2**: Factual content (contextualization)
158
- - **0.3**: Structured tasks (word selection, batch processing)
159
  - **0.6**: Creative tasks (hint generation)
160
 
161
  ## Response Processing
 
6
 
7
  The Cloze Reader uses OpenRouter's API with the `google/gemma-3-27b-it:free` model to power various AI-driven features. All requests use a consistent retry mechanism with exponential backoff (3 attempts, 0.5s initial delay).
8
 
9
+ ## Difficulty Progression
10
+
11
+ The game uses a level-based system to control difficulty:
12
+
13
+ ### Blank Count by Level
14
+ - **Levels 1-5**: 1 blank per passage
15
+ - **Levels 6-10**: 2 blanks per passage
16
+ - **Level 11+**: 3 blanks per passage
17
+
18
+ ### Word Length Constraints by Level
19
+ - **Levels 1-3**: 3-10 letters (easier, shorter words)
20
+ - **Levels 4+**: 5-13 letters (longer, more challenging words)
21
+
22
+ ### Hint System by Level
23
+ - **Levels 1-2**: Shows word length, first letter, and last letter
24
+ - **Level 3+**: Shows word length and first letter only
25
+
26
  ## Request Types
27
 
28
  ### 1. Contextual Hint Generation
 
72
  "messages": [
73
  {
74
  "role": "user",
75
+ "content": "You are a cluemaster vocabulary selector for educational cloze exercises. Select exactly [COUNT] words from this passage for a cloze exercise.\n\nCLOZE DELETION PRINCIPLES:\n- Select words that require understanding context and vocabulary to identify\n- Choose words essential for comprehension that test language ability\n- Target words where deletion creates meaningful cognitive gaps\n\nREQUIREMENTS:\n- Choose clear, properly-spelled words (no OCR errors like \"andsatires\")\n- Select meaningful nouns, verbs, or adjectives ([WORD_LENGTH] letters)\n- Words must appear EXACTLY as written in the passage\n- Avoid: capitalized words, function words, archaic terms, proper nouns, technical jargon\n- Skip any words that look malformed or concatenated\n\nReturn ONLY a JSON array of the selected words.\n\nPassage: \"[PASSAGE_TEXT]\""
76
  }
77
  ],
78
  "max_tokens": 100,
79
+ "temperature": 0.3
80
  }
81
  ```
82
 
83
+ **Word Length by Level:**
84
+ - Levels 1-3: 3-10 letters
85
+ - Levels 4+: 5-13 letters
86
+
87
  **Response Format:** JSON array of strings
88
  ```json
89
  ["word1", "word2", "word3"]
 
104
  },
105
  {
106
  "role": "user",
107
+ "content": "Process these two passages for cloze exercises:\n\nPASSAGE 1:\nTitle: \"[BOOK1_TITLE]\" by [BOOK1_AUTHOR]\nText: \"[PASSAGE1_TEXT]\"\nSelect [COUNT] words for blanks.\n\nPASSAGE 2:\nTitle: \"[BOOK2_TITLE]\" by [BOOK2_AUTHOR]\nText: \"[PASSAGE2_TEXT]\"\nSelect [COUNT] words for blanks.\n\nWORD SELECTION CRITERIA:\n[WORD_LENGTH_CRITERIA]\n- Choose meaningful nouns, verbs, or adjectives\n- Avoid capitalized words, function words, archaic terms\n- Words must appear EXACTLY as written in the passage\n\nFor each passage return:\n- \"words\": array of selected words (exactly as they appear)\n- \"context\": one-sentence intro about the book/author\n\nReturn as JSON: {\"passage1\": {...}, \"passage2\": {...}}"
108
  }
109
  ],
110
+ "max_tokens": 800,
111
  "temperature": 0.5
112
  }
113
  ```
114
 
115
+ **Word Length by Level:**
116
+ - Levels 1-3: Select words 3-10 letters long
117
+ - Levels 4+: Select words 5-13 letters long
118
+
119
  **Response Format:**
120
  ```json
121
  {
 
175
  |---------|------------|-------------|-------------|
176
  | Hints | 50 | 0.6 | 3 attempts |
177
  | Word Selection | 100 | 0.3 | 3 attempts |
178
+ | Batch Processing | 800 | 0.5 | 3 attempts |
179
+ | Contextualization | 80 | 0.5 | 3 attempts |
180
 
181
  ### Temperature Guidelines
182
+ - **0.3**: Structured tasks (word selection)
183
+ - **0.5**: Semi-structured tasks (batch processing, contextualization)
184
  - **0.6**: Creative tasks (hint generation)
185
 
186
  ## Response Processing
index.html CHANGED
@@ -7,6 +7,7 @@
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap" rel="stylesheet">
9
  <link href="./src/styles.css" rel="stylesheet">
 
10
  </head>
11
  <body class="min-h-screen">
12
  <div id="app" class="container mx-auto px-4 py-8 max-w-4xl">
 
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap" rel="stylesheet">
9
  <link href="./src/styles.css" rel="stylesheet">
10
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
11
  </head>
12
  <body class="min-h-screen">
13
  <div id="app" class="container mx-auto px-4 py-8 max-w-4xl">
package.json CHANGED
@@ -30,9 +30,8 @@
30
  "devDependencies": {
31
  "http-server": "^14.1.1"
32
  },
33
- "dependencies": {},
34
  "engines": {
35
  "node": ">=14.0.0",
36
  "python": ">=3.9"
37
  }
38
- }
 
30
  "devDependencies": {
31
  "http-server": "^14.1.1"
32
  },
 
33
  "engines": {
34
  "node": ">=14.0.0",
35
  "python": ">=3.9"
36
  }
37
+ }
src/aiService.js CHANGED
@@ -151,8 +151,10 @@ REQUIREMENTS:
151
  - Choose clear, properly-spelled words (no OCR errors like "andsatires")
152
  - Select meaningful nouns, verbs, or adjectives (4-12 letters)
153
  - Words must appear EXACTLY as written in the passage
154
- - Avoid: capitalized words, function words, archaic terms, proper nouns, technical jargon
155
  - Skip any words that look malformed or concatenated
 
 
156
 
157
  Return ONLY a JSON array of the selected words.
158
 
@@ -243,10 +245,20 @@ Title: "${book2.title}" by ${book2.author}
243
  Text: "${passage2}"
244
  Select ${blanksPerPassage} words for blanks.
245
 
 
 
 
 
 
 
 
 
246
  For each passage return:
247
- - "words": array of selected words (exactly as they appear)
248
  - "context": one-sentence intro about the book/author
249
 
 
 
250
  Return as JSON: {"passage1": {...}, "passage2": {...}}`
251
  }],
252
  max_tokens: 800,
@@ -287,6 +299,9 @@ Return as JSON: {"passage1": {...}, "passage2": {...}}`
287
  .trim();
288
 
289
  // Try to fix common JSON issues
 
 
 
290
  // Check for truncated strings (unterminated quotes)
291
  const quoteCount = (jsonString.match(/"/g) || []).length;
292
  if (quoteCount % 2 !== 0) {
@@ -325,6 +340,10 @@ Return as JSON: {"passage1": {...}, "passage2": {...}}`
325
  parsed.passage2.words = [];
326
  }
327
 
 
 
 
 
328
  return parsed;
329
  } catch (e) {
330
  console.error('Failed to parse batch response:', e);
 
151
  - Choose clear, properly-spelled words (no OCR errors like "andsatires")
152
  - Select meaningful nouns, verbs, or adjectives (4-12 letters)
153
  - Words must appear EXACTLY as written in the passage
154
+ - Avoid: capitalized words, ALL-CAPS words, function words, archaic terms, proper nouns, technical jargon
155
  - Skip any words that look malformed or concatenated
156
+ - NEVER select words from the first or last sentence/clause of the passage
157
+ - Choose words from the middle portions for better context dependency
158
 
159
  Return ONLY a JSON array of the selected words.
160
 
 
245
  Text: "${passage2}"
246
  Select ${blanksPerPassage} words for blanks.
247
 
248
+ SELECTION RULES:
249
+ - Select EXACTLY ${blanksPerPassage} word${blanksPerPassage > 1 ? 's' : ''} per passage, no more, no less
250
+ - Choose meaningful nouns, verbs, or adjectives (4-12 letters)
251
+ - Avoid capitalized words, ALL-CAPS words, and table of contents entries
252
+ - NEVER select words from the first or last sentence/clause of each passage
253
+ - Choose words from the middle portions for better context dependency
254
+ - Words must appear EXACTLY as written in the passage
255
+
256
  For each passage return:
257
+ - "words": array of EXACTLY ${blanksPerPassage} selected word${blanksPerPassage > 1 ? 's' : ''} (exactly as they appear in the text)
258
  - "context": one-sentence intro about the book/author
259
 
260
+ CRITICAL: The "words" array must contain exactly ${blanksPerPassage} element${blanksPerPassage > 1 ? 's' : ''} for each passage.
261
+
262
  Return as JSON: {"passage1": {...}, "passage2": {...}}`
263
  }],
264
  max_tokens: 800,
 
299
  .trim();
300
 
301
  // Try to fix common JSON issues
302
+ // Fix trailing commas in arrays
303
+ jsonString = jsonString.replace(/,(\s*])/g, '$1');
304
+
305
  // Check for truncated strings (unterminated quotes)
306
  const quoteCount = (jsonString.match(/"/g) || []).length;
307
  if (quoteCount % 2 !== 0) {
 
340
  parsed.passage2.words = [];
341
  }
342
 
343
+ // Filter out empty strings from words arrays (caused by trailing commas)
344
+ parsed.passage1.words = parsed.passage1.words.filter(word => word && word.trim() !== '');
345
+ parsed.passage2.words = parsed.passage2.words.filter(word => word && word.trim() !== '');
346
+
347
  return parsed;
348
  } catch (e) {
349
  console.error('Failed to parse batch response:', e);
src/app.js CHANGED
@@ -204,7 +204,7 @@ class App {
204
  async handleNext() {
205
  try {
206
  // Show loading immediately with specific message
207
- this.showLoading(true, 'Loading passage...');
208
 
209
  // Clear chat history when starting new passage/round
210
  this.chatUI.clearChatHistory();
 
204
  async handleNext() {
205
  try {
206
  // Show loading immediately with specific message
207
+ this.showLoading(true, 'Loading passages...');
208
 
209
  // Clear chat history when starting new passage/round
210
  this.chatUI.clearChatHistory();
src/clozeGameEngine.js CHANGED
@@ -19,6 +19,7 @@ class ClozeGame {
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
@@ -52,11 +53,11 @@ class ClozeGame {
52
  this.currentPassageIndex = 0;
53
 
54
  // Calculate blanks per passage based on level
55
- // Progressive difficulty: levels 1-2 = 1 blank, levels 3-4 = 2 blanks, level 5+ = 3 blanks
56
  let blanksPerPassage;
57
- if (this.currentLevel <= 2) {
58
  blanksPerPassage = 1;
59
- } else if (this.currentLevel <= 4) {
60
  blanksPerPassage = 2;
61
  } else {
62
  blanksPerPassage = 3;
@@ -65,12 +66,17 @@ class ClozeGame {
65
  // Process both passages in a single API call
66
  try {
67
  const batchResult = await aiService.processBothPassages(
68
- passage1, book1, passage2, book2, blanksPerPassage
69
  );
70
 
71
  // Store the preprocessed data for both passages
72
  this.preprocessedData = batchResult;
73
 
 
 
 
 
 
74
  // Set up first passage using preprocessed data
75
  this.currentBook = book1;
76
  this.originalText = this.passages[0];
@@ -186,20 +192,49 @@ class ClozeGame {
186
  async createClozeTextFromPreprocessed(passageIndex) {
187
  // Use preprocessed word selection from batch API call
188
  const preprocessed = passageIndex === 0 ? this.preprocessedData.passage1 : this.preprocessedData.passage2;
189
- const selectedWords = preprocessed.words;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  // Split passage into words
192
  const words = this.originalText.split(/(\s+)/);
193
  const wordsOnly = words.filter(w => w.trim() !== '');
194
 
195
- // Find indices of selected words
196
  const selectedIndices = [];
197
  selectedWords.forEach(word => {
198
- const index = wordsOnly.findIndex((w, idx) =>
199
- w.toLowerCase().includes(word.toLowerCase()) && !selectedIndices.includes(idx)
200
- );
 
 
 
 
 
 
 
 
 
 
 
201
  if (index !== -1) {
202
  selectedIndices.push(index);
 
 
203
  }
204
  });
205
 
@@ -208,6 +243,8 @@ class ClozeGame {
208
  this.hints = [];
209
  const clozeWords = [...wordsOnly];
210
 
 
 
211
  selectedIndices.forEach((wordIndex, blankIndex) => {
212
  const originalWord = wordsOnly[wordIndex];
213
  const cleanWord = originalWord.replace(/[^\w]/g, '');
@@ -259,11 +296,11 @@ class ClozeGame {
259
 
260
  async createClozeText() {
261
  const words = this.originalText.split(' ');
262
- // Progressive difficulty: levels 1-2 = 1 blank, levels 3-4 = 2 blanks, level 5+ = 3 blanks
263
  let numberOfBlanks;
264
- if (this.currentLevel <= 2) {
265
  numberOfBlanks = 1;
266
- } else if (this.currentLevel <= 4) {
267
  numberOfBlanks = 2;
268
  } else {
269
  numberOfBlanks = 3;
@@ -277,7 +314,8 @@ class ClozeGame {
277
  try {
278
  significantWords = await aiService.selectSignificantWords(
279
  this.originalText,
280
- numberOfBlanks
 
281
  );
282
  console.log('AI selected words:', significantWords);
283
  } catch (error) {
@@ -580,6 +618,9 @@ class ClozeGame {
580
 
581
  // Store results for potential answer revelation
582
  this.lastResults = resultsData;
 
 
 
583
 
584
  return resultsData;
585
  }
@@ -616,7 +657,7 @@ class ClozeGame {
616
  // Clear chat conversations for new passage
617
  this.chatService.clearConversations();
618
 
619
- // Clear last results
620
  this.lastResults = null;
621
 
622
  // Use preprocessed data if available
@@ -651,14 +692,21 @@ class ClozeGame {
651
  }
652
 
653
  nextRound() {
654
- // Check if user passed the previous round
655
- const passed = this.lastResults && this.lastResults.passed;
 
 
 
 
 
 
 
656
 
657
  // Always increment round counter
658
  this.currentRound++;
659
 
660
- // Only advance level if user passed
661
- if (passed) {
662
  this.currentLevel++;
663
  }
664
  // If failed, stay at same level
@@ -666,8 +714,9 @@ class ClozeGame {
666
  // Clear chat conversations for new round
667
  this.chatService.clearConversations();
668
 
669
- // Clear last results since we're moving to new round
670
  this.lastResults = null;
 
671
 
672
  return this.startNewRound();
673
  }
 
19
  this.hints = [];
20
  this.chatService = new ChatService(aiService);
21
  this.lastResults = null; // Store results for answer revelation
22
+ this.roundResults = []; // Store results for both passages in current round
23
 
24
  // Two-passage system properties
25
  this.currentBooks = []; // Array of two books per round
 
53
  this.currentPassageIndex = 0;
54
 
55
  // Calculate blanks per passage based on level
56
+ // Levels 1-5: 1 blank, Levels 6-10: 2 blanks, Level 11+: 3 blanks
57
  let blanksPerPassage;
58
+ if (this.currentLevel <= 5) {
59
  blanksPerPassage = 1;
60
+ } else if (this.currentLevel <= 10) {
61
  blanksPerPassage = 2;
62
  } else {
63
  blanksPerPassage = 3;
 
66
  // Process both passages in a single API call
67
  try {
68
  const batchResult = await aiService.processBothPassages(
69
+ passage1, book1, passage2, book2, blanksPerPassage, this.currentLevel
70
  );
71
 
72
  // Store the preprocessed data for both passages
73
  this.preprocessedData = batchResult;
74
 
75
+ // Debug: Log what the AI returned
76
+ console.log(`Level ${this.currentLevel}: Requested ${blanksPerPassage} blanks per passage`);
77
+ console.log(`Passage 1 received ${batchResult.passage1.words.length} words:`, batchResult.passage1.words);
78
+ console.log(`Passage 2 received ${batchResult.passage2.words.length} words:`, batchResult.passage2.words);
79
+
80
  // Set up first passage using preprocessed data
81
  this.currentBook = book1;
82
  this.originalText = this.passages[0];
 
192
  async createClozeTextFromPreprocessed(passageIndex) {
193
  // Use preprocessed word selection from batch API call
194
  const preprocessed = passageIndex === 0 ? this.preprocessedData.passage1 : this.preprocessedData.passage2;
195
+ let selectedWords = preprocessed.words;
196
+
197
+ // Calculate expected number of blanks based on level
198
+ let expectedBlanks;
199
+ if (this.currentLevel <= 5) {
200
+ expectedBlanks = 1;
201
+ } else if (this.currentLevel <= 10) {
202
+ expectedBlanks = 2;
203
+ } else {
204
+ expectedBlanks = 3;
205
+ }
206
+
207
+ // Limit selected words to expected number
208
+ if (selectedWords.length > expectedBlanks) {
209
+ console.log(`AI returned ${selectedWords.length} words but expected ${expectedBlanks}, limiting to ${expectedBlanks}`);
210
+ selectedWords = selectedWords.slice(0, expectedBlanks);
211
+ }
212
 
213
  // Split passage into words
214
  const words = this.originalText.split(/(\s+)/);
215
  const wordsOnly = words.filter(w => w.trim() !== '');
216
 
217
+ // Find indices of selected words using exact matching
218
  const selectedIndices = [];
219
  selectedWords.forEach(word => {
220
+ // First try exact match (cleaned)
221
+ let index = wordsOnly.findIndex((w, idx) => {
222
+ const cleanW = w.replace(/[^\w]/g, '').toLowerCase();
223
+ const cleanWord = word.replace(/[^\w]/g, '').toLowerCase();
224
+ return cleanW === cleanWord && !selectedIndices.includes(idx);
225
+ });
226
+
227
+ // Fallback to includes match if exact fails
228
+ if (index === -1) {
229
+ index = wordsOnly.findIndex((w, idx) =>
230
+ w.toLowerCase().includes(word.toLowerCase()) && !selectedIndices.includes(idx)
231
+ );
232
+ }
233
+
234
  if (index !== -1) {
235
  selectedIndices.push(index);
236
+ } else {
237
+ console.warn(`Could not find word "${word}" in passage`);
238
  }
239
  });
240
 
 
243
  this.hints = [];
244
  const clozeWords = [...wordsOnly];
245
 
246
+ console.log(`Creating ${selectedIndices.length} blanks from ${selectedWords.length} selected words`);
247
+
248
  selectedIndices.forEach((wordIndex, blankIndex) => {
249
  const originalWord = wordsOnly[wordIndex];
250
  const cleanWord = originalWord.replace(/[^\w]/g, '');
 
296
 
297
  async createClozeText() {
298
  const words = this.originalText.split(' ');
299
+ // Progressive difficulty: levels 1-5 = 1 blank, levels 6-10 = 2 blanks, level 11+ = 3 blanks
300
  let numberOfBlanks;
301
+ if (this.currentLevel <= 5) {
302
  numberOfBlanks = 1;
303
+ } else if (this.currentLevel <= 10) {
304
  numberOfBlanks = 2;
305
  } else {
306
  numberOfBlanks = 3;
 
314
  try {
315
  significantWords = await aiService.selectSignificantWords(
316
  this.originalText,
317
+ numberOfBlanks,
318
+ this.currentLevel
319
  );
320
  console.log('AI selected words:', significantWords);
321
  } catch (error) {
 
618
 
619
  // Store results for potential answer revelation
620
  this.lastResults = resultsData;
621
+
622
+ // Store results for round-level tracking
623
+ this.roundResults[this.currentPassageIndex] = resultsData;
624
 
625
  return resultsData;
626
  }
 
657
  // Clear chat conversations for new passage
658
  this.chatService.clearConversations();
659
 
660
+ // Clear last results (but keep roundResults for level advancement)
661
  this.lastResults = null;
662
 
663
  // Use preprocessed data if available
 
692
  }
693
 
694
  nextRound() {
695
+ // Check if user passed the previous round based on overall round performance
696
+ let roundPassed = false;
697
+ if (this.roundResults.length === 2) {
698
+ // Both passages completed - check if user passed at least one passage
699
+ roundPassed = this.roundResults.some(result => result && result.passed);
700
+ } else if (this.lastResults) {
701
+ // Fallback to single passage result
702
+ roundPassed = this.lastResults.passed;
703
+ }
704
 
705
  // Always increment round counter
706
  this.currentRound++;
707
 
708
+ // Only advance level if user passed the round
709
+ if (roundPassed) {
710
  this.currentLevel++;
711
  }
712
  // If failed, stay at same level
 
714
  // Clear chat conversations for new round
715
  this.chatService.clearConversations();
716
 
717
+ // Clear results since we're moving to new round
718
  this.lastResults = null;
719
+ this.roundResults = [];
720
 
721
  return this.startNewRound();
722
  }
src/init-env.js CHANGED
@@ -13,7 +13,5 @@ document.addEventListener('DOMContentLoaded', function() {
13
  if (hfMeta && hfMeta.content) {
14
  window.HF_API_KEY = hfMeta.content;
15
  console.log('HF API key loaded');
16
- } else {
17
- console.log('No HF API key found in meta tags');
18
  }
19
  });
 
13
  if (hfMeta && hfMeta.content) {
14
  window.HF_API_KEY = hfMeta.content;
15
  console.log('HF API key loaded');
 
 
16
  }
17
  });