Spaces:
Running
Running
improve: differentiate prompts, fix passage boundaries, and avoid early blanks
Browse files- Make each question type distinct with specific instructions and word limits
- Ensure passages end at complete sentences, not mid-sentence
- Prevent blanks from appearing in first 10 words of passage
- Extract longer passages (1000 chars) for better sentence completion
- Add retry logic if extracted passage is too short
- src/clozeGameEngine.js +23 -15
- src/conversationManager.js +4 -4
src/clozeGameEngine.js
CHANGED
|
@@ -70,11 +70,11 @@ class ClozeGame {
|
|
| 70 |
|
| 71 |
// Random position in the middle section
|
| 72 |
const availableLength = endAtThreeQuarters - startFromMiddle;
|
| 73 |
-
const randomOffset = Math.floor(Math.random() * Math.max(0, availableLength -
|
| 74 |
const startIndex = startFromMiddle + randomOffset;
|
| 75 |
|
| 76 |
-
// Extract passage
|
| 77 |
-
let passage = text.substring(startIndex, startIndex +
|
| 78 |
|
| 79 |
// Clean up start - find first complete sentence
|
| 80 |
const firstSentenceEnd = passage.search(/[.!?]\s+[A-Z]/);
|
|
@@ -82,10 +82,18 @@ class ClozeGame {
|
|
| 82 |
passage = passage.substring(firstSentenceEnd + 2);
|
| 83 |
}
|
| 84 |
|
| 85 |
-
// Clean up end - end at complete sentence
|
| 86 |
-
const
|
| 87 |
-
if (
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
|
| 91 |
return passage.trim();
|
|
@@ -143,18 +151,18 @@ class ClozeGame {
|
|
| 143 |
|
| 144 |
let wordIndex = -1;
|
| 145 |
|
| 146 |
-
// First try to find the word in the designated section
|
| 147 |
-
for (let i = sectionStart; i < sectionEnd; i++) {
|
| 148 |
if (wordsLower[i] === cleanSignificant && !selectedIndices.includes(i)) {
|
| 149 |
wordIndex = i;
|
| 150 |
break;
|
| 151 |
}
|
| 152 |
}
|
| 153 |
|
| 154 |
-
// If not found in section, look globally
|
| 155 |
if (wordIndex === -1) {
|
| 156 |
wordIndex = wordsLower.findIndex((word, idx) =>
|
| 157 |
-
word === cleanSignificant && !selectedIndices.includes(idx)
|
| 158 |
);
|
| 159 |
}
|
| 160 |
|
|
@@ -173,11 +181,11 @@ class ClozeGame {
|
|
| 173 |
console.warn('No AI words matched in passage, using manual selection');
|
| 174 |
const manualWords = this.selectWordsManually(words, numberOfBlanks);
|
| 175 |
|
| 176 |
-
// Try to match manual words
|
| 177 |
manualWords.forEach((manualWord, index) => {
|
| 178 |
const cleanManual = manualWord.toLowerCase().replace(/[^\w]/g, '');
|
| 179 |
const wordIndex = wordsLower.findIndex((word, idx) =>
|
| 180 |
-
word === cleanManual && !selectedIndices.includes(idx)
|
| 181 |
);
|
| 182 |
|
| 183 |
if (wordIndex !== -1) {
|
|
@@ -191,11 +199,11 @@ class ClozeGame {
|
|
| 191 |
// Sort indices for easier processing
|
| 192 |
selectedIndices.sort((a, b) => a - b);
|
| 193 |
|
| 194 |
-
// Final safety check - if still no words found, pick random content words
|
| 195 |
if (selectedIndices.length === 0) {
|
| 196 |
console.error('Critical: No words could be selected, using emergency fallback');
|
| 197 |
const contentWords = words.map((word, idx) => ({ word: word.toLowerCase().replace(/[^\w]/g, ''), idx }))
|
| 198 |
-
.filter(item => item.word.length > 3 && !['the', 'and', 'but', 'for', 'are', 'was'].includes(item.word))
|
| 199 |
.slice(0, numberOfBlanks);
|
| 200 |
|
| 201 |
selectedIndices.push(...contentWords.map(item => item.idx));
|
|
|
|
| 70 |
|
| 71 |
// Random position in the middle section
|
| 72 |
const availableLength = endAtThreeQuarters - startFromMiddle;
|
| 73 |
+
const randomOffset = Math.floor(Math.random() * Math.max(0, availableLength - 1000));
|
| 74 |
const startIndex = startFromMiddle + randomOffset;
|
| 75 |
|
| 76 |
+
// Extract longer initial passage for better sentence completion
|
| 77 |
+
let passage = text.substring(startIndex, startIndex + 1000);
|
| 78 |
|
| 79 |
// Clean up start - find first complete sentence
|
| 80 |
const firstSentenceEnd = passage.search(/[.!?]\s+[A-Z]/);
|
|
|
|
| 82 |
passage = passage.substring(firstSentenceEnd + 2);
|
| 83 |
}
|
| 84 |
|
| 85 |
+
// Clean up end - ensure we end at a complete sentence
|
| 86 |
+
const sentences = passage.split(/(?<=[.!?])\s+/);
|
| 87 |
+
if (sentences.length > 1) {
|
| 88 |
+
// Remove the last sentence if it might be incomplete
|
| 89 |
+
sentences.pop();
|
| 90 |
+
passage = sentences.join(' ');
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// Ensure minimum length
|
| 94 |
+
if (passage.length < 400) {
|
| 95 |
+
// Try again with different position if too short
|
| 96 |
+
return this.extractCoherentPassage(text);
|
| 97 |
}
|
| 98 |
|
| 99 |
return passage.trim();
|
|
|
|
| 151 |
|
| 152 |
let wordIndex = -1;
|
| 153 |
|
| 154 |
+
// First try to find the word in the designated section (avoiding first 10 words)
|
| 155 |
+
for (let i = Math.max(10, sectionStart); i < sectionEnd; i++) {
|
| 156 |
if (wordsLower[i] === cleanSignificant && !selectedIndices.includes(i)) {
|
| 157 |
wordIndex = i;
|
| 158 |
break;
|
| 159 |
}
|
| 160 |
}
|
| 161 |
|
| 162 |
+
// If not found in section, look globally (but still avoid first 10 words)
|
| 163 |
if (wordIndex === -1) {
|
| 164 |
wordIndex = wordsLower.findIndex((word, idx) =>
|
| 165 |
+
word === cleanSignificant && !selectedIndices.includes(idx) && idx >= 10
|
| 166 |
);
|
| 167 |
}
|
| 168 |
|
|
|
|
| 181 |
console.warn('No AI words matched in passage, using manual selection');
|
| 182 |
const manualWords = this.selectWordsManually(words, numberOfBlanks);
|
| 183 |
|
| 184 |
+
// Try to match manual words (avoiding first 10 words)
|
| 185 |
manualWords.forEach((manualWord, index) => {
|
| 186 |
const cleanManual = manualWord.toLowerCase().replace(/[^\w]/g, '');
|
| 187 |
const wordIndex = wordsLower.findIndex((word, idx) =>
|
| 188 |
+
word === cleanManual && !selectedIndices.includes(idx) && idx >= 10
|
| 189 |
);
|
| 190 |
|
| 191 |
if (wordIndex !== -1) {
|
|
|
|
| 199 |
// Sort indices for easier processing
|
| 200 |
selectedIndices.sort((a, b) => a - b);
|
| 201 |
|
| 202 |
+
// Final safety check - if still no words found, pick random content words (avoiding first 10)
|
| 203 |
if (selectedIndices.length === 0) {
|
| 204 |
console.error('Critical: No words could be selected, using emergency fallback');
|
| 205 |
const contentWords = words.map((word, idx) => ({ word: word.toLowerCase().replace(/[^\w]/g, ''), idx }))
|
| 206 |
+
.filter(item => item.word.length > 3 && !['the', 'and', 'but', 'for', 'are', 'was'].includes(item.word) && item.idx >= 10)
|
| 207 |
.slice(0, numberOfBlanks);
|
| 208 |
|
| 209 |
selectedIndices.push(...contentWords.map(item => item.idx));
|
src/conversationManager.js
CHANGED
|
@@ -106,13 +106,13 @@ class ChatService {
|
|
| 106 |
const wordInstruction = `The target word is "${targetWord}". NEVER mention or reveal this word in your response.`;
|
| 107 |
|
| 108 |
const prompts = {
|
| 109 |
-
part_of_speech: `${baseContext}\n\n${wordInstruction}\n\
|
| 110 |
|
| 111 |
-
sentence_role: `${baseContext}\n\n${wordInstruction}\n\
|
| 112 |
|
| 113 |
-
word_category: `${baseContext}\n\n${wordInstruction}\n\
|
| 114 |
|
| 115 |
-
synonym: `${baseContext}\n\n${wordInstruction}\n\nGive a
|
| 116 |
};
|
| 117 |
|
| 118 |
return prompts[questionType] || `${baseContext}\n\n${wordInstruction}\n\nProvide a helpful hint about "${targetWord}" without revealing it.`;
|
|
|
|
| 106 |
const wordInstruction = `The target word is "${targetWord}". NEVER mention or reveal this word in your response.`;
|
| 107 |
|
| 108 |
const prompts = {
|
| 109 |
+
part_of_speech: `${baseContext}\n\n${wordInstruction}\n\nState only the grammatical category: "This is a noun" or "This is a verb" etc. Then give ONE grammar rule about how this type of word works. Maximum 15 words total.`,
|
| 110 |
|
| 111 |
+
sentence_role: `${baseContext}\n\n${wordInstruction}\n\nExplain only what job "${targetWord}" does in this specific sentence. Focus on its function, not what it means. Start with "In this sentence, it..." Maximum 15 words.`,
|
| 112 |
|
| 113 |
+
word_category: `${baseContext}\n\n${wordInstruction}\n\nClassify "${targetWord}" into a broad category. Choose from: living thing, object, action, feeling, quality, place, or time. Say "This belongs to the category of..." Maximum 12 words.`,
|
| 114 |
|
| 115 |
+
synonym: `${baseContext}\n\n${wordInstruction}\n\nGive a different word that could replace "${targetWord}" in this sentence. Say "You could use the word..." Maximum 8 words. Choose a simple synonym.`
|
| 116 |
};
|
| 117 |
|
| 118 |
return prompts[questionType] || `${baseContext}\n\n${wordInstruction}\n\nProvide a helpful hint about "${targetWord}" without revealing it.`;
|