Spaces:
Sleeping
Sleeping
| // Chat service for contextual, personalized hints | |
| class ChatService { | |
| constructor(aiService) { | |
| this.aiService = aiService; | |
| this.conversations = new Map(); // blankId -> conversation history | |
| this.wordContexts = new Map(); // blankId -> detailed context | |
| this.blankQuestions = new Map(); // blankId -> Set of used question types (per-blank tracking) | |
| this.currentLevel = 1; // Track current difficulty level | |
| // Distinct, non-overlapping question set | |
| this.questions = [ | |
| { text: "What is its part of speech?", type: "part_of_speech" }, | |
| { text: "What role does it play in the sentence?", type: "sentence_role" }, | |
| { text: "Is it abstract or a person, place, or thing?", type: "word_category" }, | |
| { text: "What is a synonym for this word?", type: "synonym" } | |
| ]; | |
| } | |
| // Initialize chat context for a specific blank | |
| initializeWordContext(blankId, wordData) { | |
| const context = { | |
| blankId, | |
| targetWord: wordData.originalWord, | |
| sentence: wordData.sentence, | |
| fullPassage: wordData.passage, | |
| bookTitle: wordData.bookTitle, | |
| author: wordData.author, | |
| wordPosition: wordData.wordPosition, | |
| difficulty: wordData.difficulty, | |
| previousAttempts: [], | |
| userQuestions: [], | |
| hintLevel: 0 // Progressive hint difficulty | |
| }; | |
| this.wordContexts.set(blankId, context); | |
| this.conversations.set(blankId, []); | |
| return context; | |
| } | |
| // Per-blank question tracking with level awareness | |
| async askQuestion(blankId, questionType, userInput = '') { | |
| const context = this.wordContexts.get(blankId); | |
| if (!context) { | |
| return { | |
| error: true, | |
| message: "Context not found for this word." | |
| }; | |
| } | |
| // Mark question as used for this specific blank | |
| if (!this.blankQuestions.has(blankId)) { | |
| this.blankQuestions.set(blankId, new Set()); | |
| } | |
| this.blankQuestions.get(blankId).add(questionType); | |
| try { | |
| const response = await this.generateSpecificResponse(context, questionType, userInput); | |
| return { | |
| success: true, | |
| response: response, | |
| questionType: questionType | |
| }; | |
| } catch (error) { | |
| console.error('Chat error:', error); | |
| return this.getSimpleFallback(context, questionType); | |
| } | |
| } | |
| // Generate specific response based on question type | |
| async generateSpecificResponse(context, questionType, userInput) { | |
| const word = context.targetWord; | |
| const sentence = context.sentence; | |
| const bookTitle = context.bookTitle; | |
| const author = context.author; | |
| // Create sentence with blank for context, but tell AI the actual word | |
| const sentenceWithBlank = sentence.replace(new RegExp(`\\b${word}\\b`, 'gi'), '____'); | |
| try { | |
| // Build focused prompt that includes the target word but forbids revealing it | |
| const prompt = this.buildFocusedPrompt({ | |
| ...context, | |
| sentence: sentenceWithBlank, | |
| targetWord: word | |
| }, questionType, userInput); | |
| // Use the AI service as a simple API wrapper | |
| const aiResponse = await this.aiService.generateContextualHint(prompt); | |
| if (aiResponse && typeof aiResponse === 'string' && aiResponse.length > 10) { | |
| return aiResponse; | |
| } | |
| } catch (error) { | |
| console.warn('AI response failed:', error); | |
| } | |
| // Fallback - return simple fallback response | |
| return this.getSimpleFallback(context, questionType); | |
| } | |
| // Build focused prompt for specific question types | |
| buildFocusedPrompt(context, questionType, userInput) { | |
| const { sentence, bookTitle, author, targetWord } = context; | |
| const baseContext = `From "${bookTitle}" by ${author}: "${sentence}"`; | |
| const wordInstruction = `The target word is "${targetWord}". NEVER mention or reveal this word in your response.`; | |
| const prompts = { | |
| 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.`, | |
| 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.`, | |
| 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.`, | |
| 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.` | |
| }; | |
| return prompts[questionType] || `${baseContext}\n\n${wordInstruction}\n\nProvide a helpful hint about "${targetWord}" without revealing it.`; | |
| } | |
| // Simple fallback responses | |
| getSimpleFallback(context, questionType) { | |
| const fallbacks = { | |
| part_of_speech: "Look at the surrounding words. Is it describing something, showing action, or naming something?", | |
| sentence_role: "Consider how this word connects to the other parts of the sentence.", | |
| word_category: "Think about whether this represents something concrete or an abstract idea.", | |
| synonym: "What other word could fit in this same spot with similar meaning?" | |
| }; | |
| return fallbacks[questionType] || "Consider the context and what word would make sense here."; | |
| } | |
| // Clear conversations and reset tracking | |
| clearConversations() { | |
| this.conversations.clear(); | |
| this.wordContexts.clear(); | |
| this.blankQuestions.clear(); | |
| } | |
| // Set current level for question selection | |
| setLevel(level) { | |
| this.currentLevel = level; | |
| } | |
| // Get suggested questions for a specific blank | |
| getSuggestedQuestions(blankId) { | |
| const usedQuestions = this.blankQuestions.get(blankId) || new Set(); | |
| return this.questions.map(q => ({ | |
| ...q, | |
| used: usedQuestions.has(q.type) | |
| })); | |
| } | |
| // Reset for new game (clears everything including across-game state) | |
| resetForNewGame() { | |
| this.clearConversations(); | |
| this.currentLevel = 1; | |
| } | |
| } | |
| export default ChatService; |