Spaces:
Runtime error
Runtime error
| import Groq from 'groq-sdk'; | |
| import logger from '../utils/logger.js'; | |
| const apiKey = process.env.GROQ_API_KEY; | |
| let groq = null; | |
| if (apiKey) { | |
| try { | |
| groq = new Groq({ apiKey }); | |
| logger.info('✅ Groq SDK initialized successfully.'); | |
| } catch (err) { | |
| logger.error('❌ Failed to initialize Groq SDK:', { error: err.message }); | |
| } | |
| } else { | |
| logger.warn('⚠️ No GROQ_API_KEY found. AI endpoints will run in Mock Fallback mode.'); | |
| } | |
| // Simple in-memory cache for the daily eco tip | |
| let dailyTipCache = { | |
| date: null, | |
| tip: null, | |
| }; | |
| export const aiService = { | |
| /** | |
| * Conversational Eco-Coach Chatbot | |
| */ | |
| chat: async (message, context = {}) => { | |
| if (!groq) { | |
| return { | |
| reply: `Hi! I'm your CarbonSense Eco Coach. Currently, I'm running in offline demonstration mode. To unlock my full AI intelligence capabilities, please add a valid \`GROQ_API_KEY\` to your backend environment settings.\n\nBased on your message ("${message}"), my recommendation is to reduce daily car travel and review electricity usage!`, | |
| suggestedActions: [ | |
| 'Turn off unused appliances', | |
| 'Consider public transport', | |
| 'Explore green electricity tariffs', | |
| ], | |
| }; | |
| } | |
| try { | |
| const systemPrompt = `You are CarbonSense Ocean Intelligence Coach, an expert sustainability advisor. | |
| You are helping a user reduce their carbon footprint. | |
| Be encouraging, professional, and practical. Use bullet points for recommendations. | |
| Keep answers concise (max 3 short paragraphs). | |
| ${ | |
| context.footprint | |
| ? `Current User Footprint Context: | |
| - Total Annual Footprint: ${context.footprint} kg CO2e | |
| - Breakdown: Transport: ${context.breakdown?.transport || 0}%, Electricity: ${context.breakdown?.electricity || 0}%, Food: ${context.breakdown?.food || 0}%, Shopping: ${context.breakdown?.shopping || 0}%` | |
| : '' | |
| }`; | |
| const chatCompletion = await groq.chat.completions.create({ | |
| messages: [ | |
| { role: 'system', content: systemPrompt }, | |
| { role: 'user', content: message }, | |
| ], | |
| model: 'llama-3.1-8b-instant', | |
| temperature: 0.7, | |
| max_tokens: 350, | |
| }); | |
| const reply = chatCompletion.choices[0]?.message?.content || 'No response generated.'; | |
| return { | |
| reply, | |
| suggestedActions: [ | |
| 'Calculate footprint again', | |
| 'Optimize my energy contract', | |
| 'Find cyclable commuting routes', | |
| ], | |
| }; | |
| } catch (err) { | |
| logger.error('Groq Chat completion failed:', { error: err.message || err }); | |
| return { | |
| reply: | |
| 'I apologize, but my connection to the AI engine timed out. Please try again in a few moments!', | |
| suggestedActions: [], | |
| }; | |
| } | |
| }, | |
| /** | |
| * Personalized 30-Day Net Zero Action Plan | |
| */ | |
| generatePlan: async (footprint, breakdown, goal = '20% reduction') => { | |
| if (!groq) { | |
| return { | |
| plan: [ | |
| { | |
| week: 1, | |
| actions: [ | |
| 'Switch to 100% renewable electricity tariff', | |
| 'Swap one meat meal daily for a plant-based alternative', | |
| ], | |
| expectedReduction: 'Approx. 50 kg', | |
| }, | |
| { | |
| week: 2, | |
| actions: [ | |
| 'Walk or cycle for all trips under 3 km', | |
| 'Unplug standby electronics when sleeping', | |
| ], | |
| expectedReduction: 'Approx. 30 kg', | |
| }, | |
| { | |
| week: 3, | |
| actions: [ | |
| 'Wash clothes at 30°C and line-dry instead of tumble drying', | |
| 'Avoid purchasing fast-fashion clothing items', | |
| ], | |
| expectedReduction: 'Approx. 40 kg', | |
| }, | |
| { | |
| week: 4, | |
| actions: [ | |
| 'Replace three short regional drives with public transit', | |
| 'Conduct a home energy audit', | |
| ], | |
| expectedReduction: 'Approx. 60 kg', | |
| }, | |
| ], | |
| goal, | |
| }; | |
| } | |
| try { | |
| const prompt = `Generate a structured, personalized 30-day net-zero action plan for a user to achieve their goal of "${goal}". | |
| Annual Footprint: ${footprint} kg CO2e. | |
| Category breakdown: Transport: ${breakdown.transport || 0}%, Energy: ${breakdown.energy || 0}%, Food: ${breakdown.food || 0}%, Shopping: ${breakdown.shopping || 0}%. | |
| Return ONLY a valid JSON object matching this structure: | |
| { | |
| "plan": [ | |
| { | |
| "week": 1, | |
| "actions": ["action 1", "action 2"], | |
| "expectedReduction": "X kg" | |
| }, | |
| ... | |
| ] | |
| } | |
| Ensure it is valid parseable JSON. Do not include markdown code block syntax in your response, just raw JSON.`; | |
| const chatCompletion = await groq.chat.completions.create({ | |
| messages: [{ role: 'user', content: prompt }], | |
| model: 'llama-3.1-8b-instant', | |
| response_format: { type: 'json_object' }, | |
| temperature: 0.5, | |
| max_tokens: 500, | |
| }); | |
| return JSON.parse(chatCompletion.choices[0]?.message?.content || '{}'); | |
| } catch (err) { | |
| logger.error('Groq Plan generation failed:', { error: err.message || err }); | |
| return { plan: [], error: 'Failed to generate plan.' }; | |
| } | |
| }, | |
| /** | |
| * Daily Eco Tip (Cached 24h) | |
| */ | |
| getDailyTip: async () => { | |
| const today = new Date().toISOString().split('T')[0]; | |
| if (dailyTipCache.date === today && dailyTipCache.tip) { | |
| return dailyTipCache.tip; | |
| } | |
| const defaultTip = { | |
| tip: 'Wash laundry in cold water (30°C or below). This saves up to 75-90% of the energy used in a cycle, which goes solely toward heating the water!', | |
| category: 'energy', | |
| impact: 'Up to 75kg CO2/year saved', | |
| icon: 'local-laundry-service', | |
| }; | |
| if (!groq) { | |
| dailyTipCache = { date: today, tip: defaultTip }; | |
| return defaultTip; | |
| } | |
| try { | |
| const prompt = `Generate a practical, fresh daily eco tip for carbon footprint reduction. | |
| Return ONLY a valid JSON object matching this structure: | |
| { | |
| "tip": "Tip explanation goes here...", | |
| "category": "energy" | "transport" | "food" | "shopping", | |
| "impact": "X kg CO2 saved per year", | |
| "icon": "icon-identifier-string" | |
| }`; | |
| const chatCompletion = await groq.chat.completions.create({ | |
| messages: [{ role: 'user', content: prompt }], | |
| model: 'llama-3.1-8b-instant', | |
| response_format: { type: 'json_object' }, | |
| temperature: 0.9, // Higher temperature for variety | |
| max_tokens: 150, | |
| }); | |
| const parsedTip = JSON.parse(chatCompletion.choices[0]?.message?.content || '{}'); | |
| dailyTipCache = { date: today, tip: parsedTip }; | |
| return parsedTip; | |
| } catch (err) { | |
| logger.error('Groq Daily Tip generation failed:', { error: err.message || err }); | |
| return defaultTip; | |
| } | |
| }, | |
| /** | |
| * Carbon Intelligence Score & Feedback | |
| */ | |
| getScoreFeedback: async (score, breakdown) => { | |
| if (!groq) { | |
| return { | |
| score, | |
| grade: score >= 80 ? 'A' : score >= 60 ? 'B' : score >= 40 ? 'C' : 'D', | |
| feedback: `You scored ${score}/100. Focus on your largest footprint contributor. Reducing meat intake and switching to green electricity tariffs are the highest-yield steps you can take.`, | |
| }; | |
| } | |
| try { | |
| const prompt = `Provide carbon intelligence feedback on a sustainability score of ${score}/100. | |
| Breakdown: Transport: ${breakdown.transport || 0}%, Energy: ${breakdown.energy || 0}%, Food: ${breakdown.food || 0}%, Shopping: ${breakdown.shopping || 0}%. | |
| Return ONLY a valid JSON object matching this structure: | |
| { | |
| "score": ${score}, | |
| "grade": "A" | "B" | "C" | "D" | "F", | |
| "feedback": "A short, actionable paragraph of feedback summarizing their emissions profiles." | |
| }`; | |
| const chatCompletion = await groq.chat.completions.create({ | |
| messages: [{ role: 'user', content: prompt }], | |
| model: 'llama-3.1-8b-instant', | |
| response_format: { type: 'json_object' }, | |
| temperature: 0.5, | |
| max_tokens: 250, | |
| }); | |
| return JSON.parse(chatCompletion.choices[0]?.message?.content || '{}'); | |
| } catch (err) { | |
| logger.error('Groq Score Feedback failed:', { error: err.message || err }); | |
| return { | |
| score, | |
| grade: 'B', | |
| feedback: 'Your emissions are moderate. Review transport and food habits.', | |
| }; | |
| } | |
| }, | |
| /** | |
| * Narrative Comparison | |
| */ | |
| getCompareNarrative: async (totalEmission, breakdown) => { | |
| if (!groq) { | |
| return { | |
| vsGlobal: totalEmission > 4700 ? 'higher' : 'lower', | |
| standoutCategory: 'transport', | |
| narrative: `Your annual footprint of ${(totalEmission / 1000).toFixed(1)} tonnes is ${totalEmission > 4700 ? 'above' : 'below'} the global average of 4.7 tonnes. Focus on choosing green modes of transport and reducing consumer electronics purchases to further lower your footprint.`, | |
| }; | |
| } | |
| try { | |
| const prompt = `Compare a user's carbon footprint of ${totalEmission} kg CO2e against the global average (4,700 kg/year). | |
| Breakdown: Transport: ${breakdown.transport || 0}%, Energy: ${breakdown.energy || 0}%, Food: ${breakdown.food || 0}%, Shopping: ${breakdown.shopping || 0}%. | |
| Return ONLY a valid JSON object matching this structure: | |
| { | |
| "vsGlobal": "higher" | "lower", | |
| "standoutCategory": "transport" | "energy" | "food" | "shopping", | |
| "narrative": "A concise paragraph (2-3 sentences) evaluating their standing and highlighting which category has the highest relative impact." | |
| }`; | |
| const chatCompletion = await groq.chat.completions.create({ | |
| messages: [{ role: 'user', content: prompt }], | |
| model: 'llama-3.1-8b-instant', | |
| response_format: { type: 'json_object' }, | |
| temperature: 0.6, | |
| max_tokens: 250, | |
| }); | |
| return JSON.parse(chatCompletion.choices[0]?.message?.content || '{}'); | |
| } catch (err) { | |
| logger.error('Groq Comparison Narrative failed:', { error: err.message || err }); | |
| return { | |
| vsGlobal: totalEmission > 4700 ? 'higher' : 'lower', | |
| standoutCategory: 'transport', | |
| narrative: 'Comparison computed successfully.', | |
| }; | |
| } | |
| }, | |
| }; | |