/** * LLM Service for Ritu AI using Gemini API */ export async function* streamGeminiResponse(prompt, history = [], contextString = "") { const apiKey = import.meta.env.VITE_GEMINI_API_KEY if (!apiKey) { yield "Error: Gemini API Key is missing. Please add VITE_GEMINI_API_KEY to your .env file." return } // 100% Free-Tier Flash models in Google AI Studio ($0 cost) const modelsToTry = [ 'gemini-3.6-flash', 'gemini-3.1-flash-lite', 'gemini-3.5-flash', 'gemini-flash-latest' ] // Format history for Gemini const contents = history.map(msg => ({ role: msg.role === 'user' ? 'user' : 'model', parts: [{ text: msg.content }] })) // Add the current prompt contents.push({ role: 'user', parts: [{ text: prompt }] }) const systemInstruction = { parts: [{ text: "You are Ritu AI, an advanced meteorological and climate analysis assistant created by ISRO. You specialize in weather data, forecasts, and climate trends specifically for the states of Maharashtra, Goa, and Karnataka, India.\n\n" + (contextString ? `LIVE CONTEXT SENSOR DATA (Treat this as the absolute truth for the current state):\n${contextString}\n\n` : "") + "CORE INSTRUCTIONS:\n1. ONLY provide weather info and analysis for cities within Maharashtra, Goa, and Karnataka. If the user asks about ANY city outside these three states (such as Chennai, Delhi, etc.), you MUST politely decline and state that you only support Maharashtra, Goa, and Karnataka.\n2. Answer the user's prompt fully and thoroughly. If they ask for a detailed analysis or historical trends, provide a comprehensive, multi-paragraph response.\n3. Do NOT use markdown symbols like asterisks (*), hashes (#), or underscores (_) for bolding or headers. Rely entirely on clear paragraph breaks and spacing for readability.\n4. Always include EXACTLY 2 interactive follow-up questions at the very end of every single response. You MUST wrap them exactly in this format: [ACTION:Question 1] [ACTION:Question 2]. Make sure you finish generating them completely with the closing bracket ']'.\n5. You HAVE access to real-time data via the LIVE CONTEXT SENSOR DATA. NEVER complain that you cannot provide real-time updates. Use the provided context to answer questions about the current weather." }] } const body = { contents, systemInstruction, generationConfig: { temperature: 0.7, maxOutputTokens: 2048, } } let response = null let lastErrorMsg = '' for (const model of modelsToTry) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}` try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (res.ok) { response = res break } else { const errData = await res.json().catch(() => ({})) lastErrorMsg = errData?.error?.message || res.statusText || `HTTP ${res.status}` } } catch (e) { lastErrorMsg = e.message || 'Network error' } } if (!response || !response.ok) { yield `Error: Failed to connect to Gemini API. (${lastErrorMsg})` return } try { const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { value, done } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const textMatches = [...buffer.matchAll(/"text"\s*:\s*"([^"]+)"/g)] if (textMatches.length > 0) { let chunkText = '' for (const match of textMatches) { let text = match[1] .replace(/\\n/g, '\n') .replace(/\\"/g, '"') .replace(/\\\\/g, '\\') .replace(/\\t/g, '\t') chunkText += text } yield chunkText buffer = '' } } } catch (err) { console.error("Gemini stream error:", err) yield "\n\nConnection lost. Please try again." } }