Spaces:
Sleeping
Sleeping
refactor: reduce technical debt by removing unused methods and tool definitions
Browse files- Remove unused questionTools from aiService constructor
- Remove unused getEnhancedFallback and getContextualHint methods
- Remove unused getContextualization method (duplicate)
- Remove unused helper methods from conversationManager
- Clean up unused word processing and variation methods
- Maintain only essential, actively used code paths
- src/aiService.js +0 -153
- src/conversationManager.js +0 -57
src/aiService.js
CHANGED
@@ -3,54 +3,6 @@ class OpenRouterService {
|
|
3 |
this.apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
4 |
this.apiKey = this.getApiKey();
|
5 |
this.model = 'google/gemma-3-27b-it:free';
|
6 |
-
|
7 |
-
// Focused tool definitions for specific, non-overlapping question types
|
8 |
-
this.questionTools = {
|
9 |
-
part_of_speech: {
|
10 |
-
name: 'identify_part_of_speech',
|
11 |
-
description: 'Identify the grammatical category directly and clearly',
|
12 |
-
parameters: {
|
13 |
-
type: 'object',
|
14 |
-
properties: {
|
15 |
-
hint: { type: 'string', description: 'Direct answer: "This is a [noun/verb/adjective/adverb]" then add a simple, concrete clue about what type (e.g., "a thing", "an action", "describes something")' }
|
16 |
-
},
|
17 |
-
required: ['hint']
|
18 |
-
}
|
19 |
-
},
|
20 |
-
sentence_role: {
|
21 |
-
name: 'explain_sentence_role',
|
22 |
-
description: 'Explain the structural function using context clues',
|
23 |
-
parameters: {
|
24 |
-
type: 'object',
|
25 |
-
properties: {
|
26 |
-
hint: { type: 'string', description: 'Point to specific words around the blank. Example: "Look at \'the whole ___ consisting of\' - what could contain something?" Focus on the immediate context.' }
|
27 |
-
},
|
28 |
-
required: ['hint']
|
29 |
-
}
|
30 |
-
},
|
31 |
-
word_category: {
|
32 |
-
name: 'categorize_word',
|
33 |
-
description: 'State clearly if abstract or concrete',
|
34 |
-
parameters: {
|
35 |
-
type: 'object',
|
36 |
-
properties: {
|
37 |
-
hint: { type: 'string', description: 'Start simple: "This is abstract/concrete." Then give a relatable example or size clue: "Think about something very big/small" or "Like feelings/objects"' }
|
38 |
-
},
|
39 |
-
required: ['hint']
|
40 |
-
}
|
41 |
-
},
|
42 |
-
synonym: {
|
43 |
-
name: 'provide_synonym',
|
44 |
-
description: 'Give clear synonym or similar word',
|
45 |
-
parameters: {
|
46 |
-
type: 'object',
|
47 |
-
properties: {
|
48 |
-
hint: { type: 'string', description: 'Direct synonyms or word families: "Try a word similar to [related word]" or "Think of another word for [meaning]"' }
|
49 |
-
},
|
50 |
-
required: ['hint']
|
51 |
-
}
|
52 |
-
}
|
53 |
-
};
|
54 |
}
|
55 |
|
56 |
getApiKey() {
|
@@ -114,57 +66,6 @@ class OpenRouterService {
|
|
114 |
}
|
115 |
}
|
116 |
|
117 |
-
getEnhancedFallback(questionType, word, sentence, bookTitle) {
|
118 |
-
const fallbacks = {
|
119 |
-
part_of_speech: `Consider what "${word}" is doing in the sentence. Is it a person, place, thing (noun), an action (verb), or describing something (adjective)?`,
|
120 |
-
sentence_role: `Look at how "${word}" connects to other words around it. What is its job in making the sentence complete?`,
|
121 |
-
word_category: `Think about whether "${word}" is something you can touch or see (concrete) or an idea/feeling (abstract).`,
|
122 |
-
synonym: `What other word could replace "${word}" and keep the same meaning in this sentence?`
|
123 |
-
};
|
124 |
-
|
125 |
-
return fallbacks[questionType] || `Think about what "${word}" means in this classic literature context.`;
|
126 |
-
}
|
127 |
-
|
128 |
-
async getContextualHint(passage, wordToReplace, context) {
|
129 |
-
if (!this.apiKey) {
|
130 |
-
return 'API key required for contextual hints';
|
131 |
-
}
|
132 |
-
|
133 |
-
try {
|
134 |
-
const response = await fetch(this.apiUrl, {
|
135 |
-
method: 'POST',
|
136 |
-
headers: {
|
137 |
-
'Content-Type': 'application/json',
|
138 |
-
'Authorization': `Bearer ${this.apiKey}`,
|
139 |
-
'HTTP-Referer': window.location.origin,
|
140 |
-
'X-Title': 'Cloze Reader'
|
141 |
-
},
|
142 |
-
body: JSON.stringify({
|
143 |
-
model: this.model,
|
144 |
-
messages: [{
|
145 |
-
role: 'user',
|
146 |
-
content: `In this passage: "${passage}"
|
147 |
-
|
148 |
-
The word "${wordToReplace}" has been replaced with a blank. Give me a helpful hint about what word fits here, considering the context: "${context}".
|
149 |
-
|
150 |
-
Provide a brief, educational hint that helps understand the word without giving it away directly.`
|
151 |
-
}],
|
152 |
-
max_tokens: 150,
|
153 |
-
temperature: 0.7
|
154 |
-
})
|
155 |
-
});
|
156 |
-
|
157 |
-
if (!response.ok) {
|
158 |
-
throw new Error(`API request failed: ${response.status}`);
|
159 |
-
}
|
160 |
-
|
161 |
-
const data = await response.json();
|
162 |
-
return data.choices[0].message.content.trim();
|
163 |
-
} catch (error) {
|
164 |
-
console.error('Error getting contextual hint:', error);
|
165 |
-
return 'Unable to generate hint at this time';
|
166 |
-
}
|
167 |
-
}
|
168 |
|
169 |
async selectSignificantWords(passage, count) {
|
170 |
console.log('selectSignificantWords called with count:', count);
|
@@ -290,60 +191,6 @@ Passage: "${passage}"`
|
|
290 |
}
|
291 |
}
|
292 |
|
293 |
-
async getContextualization(title, author, passage) {
|
294 |
-
console.log('getContextualization called for:', title, 'by', author);
|
295 |
-
|
296 |
-
// Check for API key at runtime
|
297 |
-
const currentKey = this.getApiKey();
|
298 |
-
if (currentKey && !this.apiKey) {
|
299 |
-
this.apiKey = currentKey;
|
300 |
-
}
|
301 |
-
|
302 |
-
console.log('API key available for contextualization:', !!this.apiKey);
|
303 |
-
|
304 |
-
if (!this.apiKey) {
|
305 |
-
console.log('No API key, returning fallback contextualization');
|
306 |
-
return `📚 Practice with classic literature from ${author}'s "${title}"`;
|
307 |
-
}
|
308 |
-
|
309 |
-
try {
|
310 |
-
const response = await fetch(this.apiUrl, {
|
311 |
-
method: 'POST',
|
312 |
-
headers: {
|
313 |
-
'Content-Type': 'application/json',
|
314 |
-
'Authorization': `Bearer ${this.apiKey}`,
|
315 |
-
'HTTP-Referer': window.location.origin,
|
316 |
-
'X-Title': 'Cloze Reader'
|
317 |
-
},
|
318 |
-
body: JSON.stringify({
|
319 |
-
model: this.model,
|
320 |
-
messages: [{
|
321 |
-
role: 'system',
|
322 |
-
content: 'You are a literary expert providing brief educational context about classic literature. Always respond with exactly 2 sentences, no more. Avoid exaggerative adverbs. Be factual and restrained.'
|
323 |
-
}, {
|
324 |
-
role: 'user',
|
325 |
-
content: `Provide educational context for this passage from "${title}" by ${author}: "${passage}"`
|
326 |
-
}],
|
327 |
-
max_tokens: 100,
|
328 |
-
temperature: 0.3
|
329 |
-
})
|
330 |
-
});
|
331 |
-
|
332 |
-
if (!response.ok) {
|
333 |
-
const errorText = await response.text();
|
334 |
-
console.error('Contextualization API error:', response.status, errorText);
|
335 |
-
throw new Error(`API request failed: ${response.status}`);
|
336 |
-
}
|
337 |
-
|
338 |
-
const data = await response.json();
|
339 |
-
const content = data.choices[0].message.content.trim();
|
340 |
-
console.log('Contextualization received:', content);
|
341 |
-
return content;
|
342 |
-
} catch (error) {
|
343 |
-
console.error('Error getting contextualization:', error);
|
344 |
-
return `📚 Practice with classic literature from ${author}'s "${title}"`;
|
345 |
-
}
|
346 |
-
}
|
347 |
}
|
348 |
|
349 |
export { OpenRouterService as AIService };
|
|
|
3 |
this.apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
4 |
this.apiKey = this.getApiKey();
|
5 |
this.model = 'google/gemma-3-27b-it:free';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
}
|
7 |
|
8 |
getApiKey() {
|
|
|
66 |
}
|
67 |
}
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
async selectSignificantWords(passage, count) {
|
71 |
console.log('selectSignificantWords called with count:', count);
|
|
|
191 |
}
|
192 |
}
|
193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
}
|
195 |
|
196 |
export { OpenRouterService as AIService };
|
src/conversationManager.js
CHANGED
@@ -129,63 +129,6 @@ class ChatService {
|
|
129 |
};
|
130 |
}
|
131 |
|
132 |
-
// Helper method to get words before the target word
|
133 |
-
getWordsBefore(sentence, targetWord, count = 3) {
|
134 |
-
const words = sentence.split(/\s+/);
|
135 |
-
const targetIndex = words.findIndex(word =>
|
136 |
-
word.toLowerCase().replace(/[^\w]/g, '') === targetWord.toLowerCase()
|
137 |
-
);
|
138 |
-
|
139 |
-
if (targetIndex === -1) return "";
|
140 |
-
|
141 |
-
const startIndex = Math.max(0, targetIndex - count);
|
142 |
-
return words.slice(startIndex, targetIndex).join(' ');
|
143 |
-
}
|
144 |
-
|
145 |
-
// Helper method to get words after the target word
|
146 |
-
getWordsAfter(sentence, targetWord, count = 3) {
|
147 |
-
const words = sentence.split(/\s+/);
|
148 |
-
const targetIndex = words.findIndex(word =>
|
149 |
-
word.toLowerCase().replace(/[^\w]/g, '') === targetWord.toLowerCase()
|
150 |
-
);
|
151 |
-
|
152 |
-
if (targetIndex === -1) return "";
|
153 |
-
|
154 |
-
const endIndex = Math.min(words.length, targetIndex + count + 1);
|
155 |
-
return words.slice(targetIndex + 1, endIndex).join(' ');
|
156 |
-
}
|
157 |
-
|
158 |
-
// Process AI response to ensure quality and safety
|
159 |
-
processAIResponse(rawResponse, targetWord) {
|
160 |
-
let processed = rawResponse.trim();
|
161 |
-
|
162 |
-
// Remove any accidental word reveals
|
163 |
-
const variations = this.generateWordVariations(targetWord);
|
164 |
-
variations.forEach(variation => {
|
165 |
-
const regex = new RegExp(`\\b${variation}\\b`, 'gi');
|
166 |
-
processed = processed.replace(regex, '[the word]');
|
167 |
-
});
|
168 |
-
|
169 |
-
return processed;
|
170 |
-
}
|
171 |
-
|
172 |
-
// Generate word variations to avoid accidental reveals
|
173 |
-
generateWordVariations(word) {
|
174 |
-
const variations = [word.toLowerCase()];
|
175 |
-
|
176 |
-
// Add common variations
|
177 |
-
if (word.endsWith('ing')) {
|
178 |
-
variations.push(word.slice(0, -3));
|
179 |
-
}
|
180 |
-
if (word.endsWith('ed')) {
|
181 |
-
variations.push(word.slice(0, -2));
|
182 |
-
}
|
183 |
-
if (word.endsWith('s')) {
|
184 |
-
variations.push(word.slice(0, -1));
|
185 |
-
}
|
186 |
-
|
187 |
-
return variations;
|
188 |
-
}
|
189 |
|
190 |
// Clear conversations and reset tracking
|
191 |
clearConversations() {
|
|
|
129 |
};
|
130 |
}
|
131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
// Clear conversations and reset tracking
|
134 |
clearConversations() {
|