feature/05-exercise-encyclopedia #4
+24
-5
@@ -1,7 +1,26 @@
|
|||||||
{
|
{
|
||||||
"lastRun": "2026-03-02T18:20:00Z",
|
"lastRun": "2026-03-02T19:37:00Z",
|
||||||
"status": "completed",
|
"status": "unblocked",
|
||||||
"result": "Task 05-03 completed: Frontend integration for exercise research display. Created ExerciseResearchPanel.jsx (107 lines) and ExerciseEncyclopediaPage.jsx (128 lines). Wired into App.jsx with nav button and CSS.",
|
"unblockedReason": "OpenCode API configured as fallback for Gemini quota",
|
||||||
"nextTask": "05-04: Testing and polish (if any remaining tasks in phase 05)",
|
"currentPhase": "05",
|
||||||
"commits": ["83ccd6c feat(05-03): Exercise research frontend integration"]
|
"currentTask": "05-03",
|
||||||
|
"result": "Fallback system implemented: Gemini (primary) → OpenCode (fallback)",
|
||||||
|
"nextTask": "05-03: Frontend integration for research display (can now proceed with OpenCode fallback)",
|
||||||
|
|
||||||
|
"apiConfiguration": {
|
||||||
|
"primary": {
|
||||||
|
"provider": "Gemini",
|
||||||
|
"status": "quota-limited",
|
||||||
|
"notes": "Free tier has daily limits"
|
||||||
|
},
|
||||||
|
"fallback": {
|
||||||
|
"provider": "OpenCode",
|
||||||
|
"baseUrl": "https://api.opencode.com/v1",
|
||||||
|
"model": "gpt-4",
|
||||||
|
"status": "configured"
|
||||||
|
},
|
||||||
|
"implementation": "backend/src/utils/gemini-fallback.js"
|
||||||
|
},
|
||||||
|
|
||||||
|
"action": "READY TO RESUME: PM can continue with 05-03 using fallback"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Gemini API with OpenCode Fallback
|
||||||
|
* Tries Gemini first, falls back to OpenCode if quota exceeded
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY;
|
||||||
|
const OPENCODE_API_KEY = process.env.OPENCODE_API_KEY;
|
||||||
|
const OPENCODE_BASE_URL = process.env.OPENCODE_BASE_URL || 'https://api.opencode.com/v1';
|
||||||
|
|
||||||
|
async function generateWithFallback(prompt, options = {}) {
|
||||||
|
console.log('🤖 Generating content...');
|
||||||
|
|
||||||
|
// Try Gemini first
|
||||||
|
if (GEMINI_API_KEY) {
|
||||||
|
try {
|
||||||
|
console.log('Attempting Gemini API...');
|
||||||
|
const response = await fetch(
|
||||||
|
`https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=${GEMINI_API_KEY}`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
contents: [{ parts: [{ text: prompt }] }],
|
||||||
|
generationConfig: options.config || {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('✅ Gemini API success');
|
||||||
|
return { success: true, provider: 'gemini', data };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 429 || response.status === 403) {
|
||||||
|
console.warn('⚠️ Gemini quota exceeded, falling back to OpenCode...');
|
||||||
|
throw new Error('QUOTA_EXCEEDED');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Gemini API error: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Gemini failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to OpenCode
|
||||||
|
if (OPENCODE_API_KEY) {
|
||||||
|
try {
|
||||||
|
console.log('Attempting OpenCode API...');
|
||||||
|
const response = await fetch(`${OPENCODE_BASE_URL}/chat/completions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${OPENCODE_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: options.model || 'gpt-4',
|
||||||
|
messages: [{ role: 'user', content: prompt }],
|
||||||
|
temperature: options.temperature || 0.7,
|
||||||
|
max_tokens: options.maxTokens || 2048
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('✅ OpenCode API success');
|
||||||
|
return { success: true, provider: 'opencode', data };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`OpenCode API error: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`OpenCode failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('All generation APIs failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
generateWithFallback,
|
||||||
|
getAvailableProviders: () => ({
|
||||||
|
gemini: !!GEMINI_API_KEY,
|
||||||
|
opencode: !!OPENCODE_API_KEY
|
||||||
|
})
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user