import { getModelsForTask } from './aiConfig'; const API_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/'; export interface SavScanResult { scores: Record; varga: string; } /** * Vision Service for extracting structured data from JHora screenshots. */ export class VisionScanner { private static apiKey = import.meta.env.VITE_GEMINI_API_KEY; /** * Scans a Sarvastakavarga (SAV) image and returns the 4x4 grid of scores. */ static async scanSAV(base64Image: string, varga: string = 'D1'): Promise { if (!this.apiKey) { throw new Error('Gemini API key is missing. Please add VITE_GEMINI_API_KEY to your .env file.'); } // Strip data:image/png;base64, prefix if present const cleanBase64 = base64Image.replace(/^data:image\/(png|jpg|jpeg);base64,/, ''); const taskConfig = getModelsForTask('SAV_OCR'); try { // Try primary model (3.1-flash-lite) return await this.callGemini(cleanBase64, varga, taskConfig.primary); } catch (error: any) { console.warn(`Primary model (${taskConfig.primary}) failed:`, error.message); // Fallback to secondary (2.5-flash-lite) console.info(`Attempting fallback to ${taskConfig.secondary}...`); return await this.callGemini(cleanBase64, varga, taskConfig.secondary); } } private static async callGemini(base64: string, varga: string, model: string): Promise { const url = `${API_ENDPOINT}${model}:generateContent?key=${this.apiKey}`; const prompt = ` EXTRACT SARVASTAKAVARGA (SAV) GRID: 1. Look for a 12-box chart (often drawn as a 4x4 grid with an empty center) of numbers in the provided JHora screenshot. 2. This is the SAV (Sarvastakavarga) chart for the divisional chart: ${varga}, formatted as a South Indian astrology chart. 3. In the South Indian chart format, the Zodiac signs are ALWAYS in these fixed positions (read the integer from each box): - Top row (left to right): PISCES (top-left corner), ARIES, TAURUS, GEMINI (top-right corner). - Right column (continuing down): CANCER, LEO. - Bottom row (right to left): VIRGO (bottom-right corner), LIBRA, SCORPIO, SAGITTARIUS (bottom-left corner). - Left column (continuing up): CAPRICORN, AQUARIUS. 4. RETURN ONLY VALID JSON in this exact format, mapping the extracted number from each position to its respective sign: { "ARIES": number, "TAURUS": number, "GEMINI": number, "CANCER": number, "LEO": number, "VIRGO": number, "LIBRA": number, "SCORPIO": number, "SAGITTARIUS": number, "CAPRICORN": number, "AQUARIUS": number, "PISCES": number } `; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [ { text: prompt }, { inline_data: { mime_type: "image/png", data: base64 } } ] }], generationConfig: { response_mime_type: "application/json" } }) }); if (!response.ok) { const err = await response.json(); throw new Error(err.error?.message || 'Gemini API call failed'); } const json = await response.json(); const resultText = json.candidates?.[0]?.content?.parts?.[0]?.text; if (!resultText) { throw new Error('AI returned an empty response.'); } try { const scores = JSON.parse(resultText); return { scores, varga }; } catch (e) { console.error('Failed to parse AI JSON:', resultText); throw new Error('AI returned malformed JSON data.'); } } }