cloze-reader / src /conversationManager.js
milwright's picture
improve: enhance contextual prompts with robust safety rules and year integration
2f6bbd8
// 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,
year: wordData.year || null,
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, year } = context;
const yearPrefix = year ? `Published in ${year}, ` : '';
const baseContext = `${yearPrefix}from "${bookTitle}" by ${author}: "${sentence}"`;
const safetyRule = `Important: The hidden word is "${targetWord}". Never say this word directly - use "it," "this word," or "the word" instead.`;
const prompts = {
part_of_speech: `${baseContext}\n\n${safetyRule}\n\nIdentify the part of speech and share one interesting grammar tip about this type of word. Keep it conversational and under 25 words.\nExample: "It's a verb! These words show action or states of being, like 'run' or 'exist'."`,
sentence_role: `${baseContext}\n\n${safetyRule}\n\nExplain what role this word plays in the sentence - what's its job here? Be specific to THIS sentence. Keep it under 20 words and conversational.\nExample: "Here it connects two ideas, showing how one thing relates to another."`,
word_category: `${baseContext}\n\n${safetyRule}\n\nWhat general category does this word belong to? Think broadly - is it about people, things, actions, qualities, feelings, places, or ideas? Explain briefly in under 20 words.\nExample: "This word fits in the 'qualities' category - it describes how something looks or feels."`,
synonym: `${baseContext}\n\n${safetyRule}\n\nSuggest a word that could replace it in this sentence. Pick something simple and explain why it works. Under 15 words.\nExample: "You could use 'bright' here - it captures the same feeling of intensity."`
};
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;