diff --git a/backend/src/services/exaSearch.js b/backend/src/services/exaSearch.js index 2841b35..842b000 100644 --- a/backend/src/services/exaSearch.js +++ b/backend/src/services/exaSearch.js @@ -1,3 +1,5 @@ +const { generateWithFallback } = require('../utils/gemini-fallback'); + const DEFAULT_EXA_API_URL = 'https://api.exa.ai/search'; const buildSummary = (results) => { @@ -20,56 +22,106 @@ const buildSummary = (results) => { return snippets.slice(0, 3).join(' '); }; +const generateFallbackSummary = async (query, numResults) => { + try { + console.log('🔄 Fallback: Generating AI summary for query:', query); + const prompt = `Provide a concise summary of exercise form and technique for: "${query}". Include key points about proper form, common mistakes, and benefits. Keep it to 2-3 sentences.`; + + const response = await generateWithFallback(prompt, { + temperature: 0.7, + maxTokens: 256 + }); + + if (response.success && response.data) { + console.log(`✅ AI fallback summary generated (provider: ${response.provider})`); + // Extract text from the appropriate response format + let summaryText = ''; + if (response.data.response) { + summaryText = response.data.response; + } else if (response.data.choices?.[0]?.message?.content) { + summaryText = response.data.choices[0].message.content; + } + + return { + summary: summaryText, + results: [], // No source links from AI fallback + provider: response.provider + }; + } + } catch (err) { + console.error('AI fallback summary generation failed:', err.message); + } + + // Last resort: simple template + return { + summary: `Exercise information for ${query}. Consult a fitness professional for proper form and safety guidelines.`, + results: [], + provider: 'template' + }; +}; + const searchExerciseResearch = async ({ query, numResults = 5 }) => { if (!query || typeof query !== 'string') { throw new Error('Query must be a non-empty string'); } const apiKey = process.env.EXA_API_KEY; - if (!apiKey) { - throw new Error('EXA_API_KEY is not configured'); + + // Tier 1: Try Exa API if configured + if (apiKey) { + try { + console.log('📍 Tier 1: Attempting Exa search API...'); + const apiUrl = process.env.EXA_API_URL || DEFAULT_EXA_API_URL; + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-api-key': apiKey + }, + body: JSON.stringify({ + query, + numResults, + type: 'neural', + useAutoprompt: true + }) + }); + + if (!response.ok) { + throw new Error(`Exa search failed: ${response.status}`); + } + + const data = await response.json(); + const results = (data.results || []).map((result) => ({ + id: result.id, + title: result.title, + url: result.url, + snippet: Array.isArray(result.highlights) && result.highlights.length > 0 + ? result.highlights[0] + : result.snippet, + highlight: result.highlight, + publishedDate: result.publishedDate, + score: result.score + })); + + console.log('✅ Exa API success'); + return { + summary: buildSummary(results), + results, + provider: 'exa' + }; + } catch (err) { + console.warn(`⚠️ Exa API failed: ${err.message}, falling back to AI...`); + } + } else { + console.warn('⚠️ EXA_API_KEY not configured, using AI fallback...'); } - const apiUrl = process.env.EXA_API_URL || DEFAULT_EXA_API_URL; - - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'content-type': 'application/json', - 'x-api-key': apiKey - }, - body: JSON.stringify({ - query, - numResults, - type: 'neural', - useAutoprompt: true - }) - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`Exa search failed: ${response.status} ${text}`); - } - - const data = await response.json(); - const results = (data.results || []).map((result) => ({ - id: result.id, - title: result.title, - url: result.url, - snippet: Array.isArray(result.highlights) && result.highlights.length > 0 - ? result.highlights[0] - : result.snippet, - highlight: result.highlight, - publishedDate: result.publishedDate, - score: result.score - })); - - return { - summary: buildSummary(results), - results - }; + // Tier 2: Fallback to AI-generated summary + return generateFallbackSummary(query, numResults); }; module.exports = { - searchExerciseResearch + searchExerciseResearch, + generateFallbackSummary };