const DEFAULT_EXA_API_URL = 'https://api.exa.ai/search'; const buildSummary = (results) => { if (!results || results.length === 0) { return ''; } const snippets = results .map((result) => result.snippet || result.highlight) .filter(Boolean); if (snippets.length === 0) { return results .slice(0, 3) .map((result) => result.title) .filter(Boolean) .join(' ยท '); } return snippets.slice(0, 3).join(' '); }; /** * Create synthetic results for fallback scenarios * Generates plausible web search results when primary API is unavailable */ const createFallbackResults = (query, numResults = 5) => { const sources = [ { domain: 'wikipedia.org', title: `${query} - Wikipedia` }, { domain: 'youtube.com', title: `${query} Tutorial | How to Perform Correctly` }, { domain: 'fitnessforum.com', title: `Best Practices for ${query} Form and Technique` }, { domain: 'acefitness.org', title: `Exercise Guide: ${query}` }, { domain: 'stronglifts.com', title: `${query} Guide: Everything You Need to Know` }, { domain: 'bodybuilding.com', title: `${query} Exercise - Benefits and Variations` }, { domain: 'nhs.uk', title: `${query}: Health Benefits and Safety` }, { domain: 'healthline.com', title: `${query}: Technique, Benefits & Common Mistakes` } ]; return sources.slice(0, numResults).map((source, index) => ({ id: `fallback-${index}`, title: source.title, url: `https://${source.domain}/search?q=${encodeURIComponent(query)}`, snippet: `Learn about proper ${query} technique, benefits, and safety precautions.`, publishedDate: new Date().toISOString(), score: 0.8 - (index * 0.05), isFallback: true, provider: 'fallback' })); }; /** * Main research search function with Exa API + fallback support * Tier 1: Exa API (primary) * Tier 2: Fallback to synthetic results with suggested sources */ 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; const apiUrl = process.env.EXA_API_URL || DEFAULT_EXA_API_URL; // Tier 1: Try Exa API (primary) if (apiKey) { try { console.log(`๐Ÿ“ [Research] Attempting Exa API for: "${query}"`); 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 }), timeout: 30000 }); if (!response.ok) { const text = await response.text(); console.warn(`โš ๏ธ [Research] Exa API error: ${response.status}`); 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, provider: 'exa' })); console.log(`โœ… [Research] Exa API success - ${results.length} results`); return { summary: buildSummary(results), results, provider: 'exa', status: 'success' }; } catch (err) { console.warn(`โš ๏ธ [Research] Exa API failed: ${err.message}`); } } else { console.warn('โš ๏ธ [Research] EXA_API_KEY not configured, using fallback'); } // Tier 2: Fallback to synthetic results with suggested sources console.log(`๐Ÿ“ [Research] Using fallback results for: "${query}"`); const fallbackResults = createFallbackResults(query, numResults); return { summary: `Research sources for "${query}". Click links below to learn more about this exercise.`, results: fallbackResults, provider: 'fallback', status: 'degraded' }; }; module.exports = { searchExerciseResearch, createFallbackResults };