diff --git a/.pm-checkpoint.json b/.pm-checkpoint.json index 048452c..80fce39 100644 --- a/.pm-checkpoint.json +++ b/.pm-checkpoint.json @@ -1,7 +1,26 @@ { - "lastRun": "2026-03-02T18:20:00Z", - "status": "completed", - "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.", - "nextTask": "05-04: Testing and polish (if any remaining tasks in phase 05)", - "commits": ["83ccd6c feat(05-03): Exercise research frontend integration"] + "lastRun": "2026-03-02T19:37:00Z", + "status": "unblocked", + "unblockedReason": "OpenCode API configured as fallback for Gemini quota", + "currentPhase": "05", + "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" } diff --git a/backend/src/utils/gemini-fallback.js b/backend/src/utils/gemini-fallback.js new file mode 100644 index 0000000..8ad75cd --- /dev/null +++ b/backend/src/utils/gemini-fallback.js @@ -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 + }) +};