Spaces:
Running
Running
class OpenRouterService { | |
constructor() { | |
this.apiUrl = 'https://openrouter.ai/api/v1/chat/completions'; | |
this.apiKey = this.getApiKey(); | |
this.model = 'google/gemma-3-27b-it:free'; | |
} | |
getApiKey() { | |
if (typeof process !== 'undefined' && process.env && process.env.OPENROUTER_API_KEY) { | |
return process.env.OPENROUTER_API_KEY; | |
} | |
if (typeof window !== 'undefined' && window.OPENROUTER_API_KEY) { | |
return window.OPENROUTER_API_KEY; | |
} | |
console.warn('No API key found in getApiKey()'); | |
return ''; | |
} | |
setApiKey(key) { | |
this.apiKey = key; | |
} | |
async generateContextualHint(prompt) { | |
// Check for API key at runtime | |
const currentKey = this.getApiKey(); | |
if (currentKey && !this.apiKey) { | |
this.apiKey = currentKey; | |
} | |
if (!this.apiKey) { | |
return 'API key required for hints'; | |
} | |
try { | |
const response = await fetch(this.apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${this.apiKey}`, | |
'HTTP-Referer': window.location.origin, | |
'X-Title': 'Cloze Reader' | |
}, | |
body: JSON.stringify({ | |
model: this.model, | |
messages: [{ | |
role: 'system', | |
content: 'You provide clues for word puzzles. Follow the EXACT format requested. Be concise and direct. Never reveal the actual word. Use plain text only - no bold, italics, asterisks, or markdown formatting. Stick to word limits.' | |
}, { | |
role: 'user', | |
content: prompt | |
}], | |
max_tokens: 50, | |
temperature: 0.6 | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`API request failed: ${response.status}`); | |
} | |
const data = await response.json(); | |
// Check if data and choices exist before accessing | |
if (!data || !data.choices || data.choices.length === 0) { | |
console.error('Invalid API response structure:', data); | |
return 'Unable to generate hint at this time'; | |
} | |
// Check if message content exists | |
if (!data.choices[0].message || !data.choices[0].message.content) { | |
console.error('No content in API response'); | |
return 'Unable to generate hint at this time'; | |
} | |
return data.choices[0].message.content.trim(); | |
} catch (error) { | |
console.error('Error generating contextual hint:', error); | |
return 'Unable to generate hint at this time'; | |
} | |
} | |
async selectSignificantWords(passage, count) { | |
console.log('selectSignificantWords called with count:', count); | |
// Check for API key at runtime in case it was loaded after initialization | |
const currentKey = this.getApiKey(); | |
if (currentKey && !this.apiKey) { | |
this.apiKey = currentKey; | |
} | |
console.log('API key available:', !!this.apiKey); | |
if (!this.apiKey) { | |
console.error('No API key for word selection'); | |
throw new Error('API key required for word selection'); | |
} | |
try { | |
const response = await fetch(this.apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${this.apiKey}`, | |
'HTTP-Referer': window.location.origin, | |
'X-Title': 'Cloze Reader' | |
}, | |
body: JSON.stringify({ | |
model: this.model, | |
messages: [{ | |
role: 'system', | |
content: 'Select words for cloze reading exercises. Choose common, everyday words that students know. Avoid proper nouns (names, places), technical terms, archaic words, and words over 8 letters. Pick words students can guess from surrounding context.' | |
}, { | |
role: 'user', | |
content: `Select exactly ${count} appropriate words for a cloze exercise. Choose common words students can guess from context. Avoid: proper nouns, technical terms, rare/archaic words, words over 8 letters. Return ONLY a JSON array of words. | |
Passage: "${passage}"` | |
}], | |
max_tokens: 100, | |
temperature: 0.3 | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`API request failed: ${response.status}`); | |
} | |
const data = await response.json(); | |
const content = data.choices[0].message.content.trim(); | |
// Try to parse as JSON array | |
try { | |
const words = JSON.parse(content); | |
if (Array.isArray(words)) { | |
return words.slice(0, count); | |
} | |
} catch (e) { | |
// If not valid JSON, try to extract words from the response | |
const matches = content.match(/"([^"]+)"/g); | |
if (matches) { | |
return matches.map(m => m.replace(/"/g, '')).slice(0, count); | |
} | |
} | |
throw new Error('Failed to parse AI response'); | |
} catch (error) { | |
console.error('Error selecting words with AI:', error); | |
throw error; | |
} | |
} | |
async generateContextualization(title, author) { | |
console.log('generateContextualization called for:', title, 'by', author); | |
// Check for API key at runtime | |
const currentKey = this.getApiKey(); | |
if (currentKey && !this.apiKey) { | |
this.apiKey = currentKey; | |
} | |
console.log('API key available for contextualization:', !!this.apiKey); | |
if (!this.apiKey) { | |
console.log('No API key, returning fallback contextualization'); | |
return `π Practice with classic literature from ${author}'s "${title}"`; | |
} | |
try { | |
const response = await fetch(this.apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${this.apiKey}`, | |
'HTTP-Referer': window.location.origin, | |
'X-Title': 'Cloze Reader' | |
}, | |
body: JSON.stringify({ | |
model: this.model, | |
messages: [{ | |
role: 'system', | |
content: 'You are a literary expert. Provide exactly 1 short, factual sentence about this classic work. Be accurate and concise. Do not add fictional details or characters.' | |
}, { | |
role: 'user', | |
content: `Write one factual sentence about "${title}" by ${author}. Focus on what type of work it is, when it was written, or its historical significance.` | |
}], | |
max_tokens: 80, | |
temperature: 0.2 | |
}) | |
}); | |
if (!response.ok) { | |
const errorText = await response.text(); | |
console.error('Contextualization API error:', response.status, errorText); | |
throw new Error(`API request failed: ${response.status}`); | |
} | |
const data = await response.json(); | |
const content = data.choices[0].message.content.trim(); | |
console.log('Contextualization received:', content); | |
return content; | |
} catch (error) { | |
console.error('Error getting contextualization:', error); | |
return `π Practice with classic literature from ${author}'s "${title}"`; | |
} | |
} | |
} | |
export { OpenRouterService as AIService }; |