import { GoogleGenAI, Type } from "@google/genai"; import { EventCategory, CalendarEvent, AppSettings, PulseAnalysis } from "../types"; import { v4 as uuidv4 } from 'uuid'; const apiKey = process.env.API_KEY || ''; const geminiAi = new GoogleGenAI({ apiKey }); // --- Helper for Ollama --- const callOllama = async (settings: AppSettings, prompt: string, jsonMode: boolean = true) => { try { const response = await fetch(`${settings.ollamaUrl}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: settings.ollamaModel, prompt: prompt, stream: false, format: jsonMode ? "json" : undefined }) }); if (!response.ok) throw new Error("Ollama connection failed. Check your IP/Port and CORS settings."); const data = await response.json(); return jsonMode ? JSON.parse(data.response) : data.response; } catch (error) { console.error("Ollama Error:", error); throw error; } }; // --- Helper for Gemini --- const callGemini = async (prompt: string, schema?: any) => { try { const model = 'gemini-3-flash-preview'; const config: any = {}; if (schema) { config.responseMimeType = "application/json"; config.responseSchema = schema; } const response = await geminiAi.models.generateContent({ model, contents: prompt, config }); return schema ? JSON.parse(response.text || "{}") : response.text; } catch (error) { console.error("Gemini Error:", error); throw error; } }; // --- Features --- export const parseNaturalLanguageEvent = async ( input: string, settings: AppSettings ): Promise<{ title: string; startTime: string; endTime: string; description?: string; category: EventCategory } | null> => { const referenceDate = new Date(); const prompt = ` Current Date context: ${referenceDate.toISOString()} User Input: "${input}" Extract event details. Return JSON. Categorize into one of: 'general', 'chore', 'date', 'work', 'fun'. Required fields: title, startTime (ISO), endTime (ISO), category. If exact time not given, guess reasonable time. `; try { if (settings.aiProvider === 'ollama') { return await callOllama(settings, prompt, true); } else { return await callGemini(prompt, { type: Type.OBJECT, properties: { title: { type: Type.STRING }, startTime: { type: Type.STRING }, endTime: { type: Type.STRING }, description: { type: Type.STRING }, category: { type: Type.STRING, enum: ['general', 'chore', 'date', 'work', 'fun'] } }, required: ["title", "startTime", "endTime", "category"] }); } } catch (e) { console.warn("AI Event Parse Failed"); return null; } }; export const analyzeRelationshipPulse = async ( events: CalendarEvent[], settings: AppSettings ): Promise => { const now = new Date(); const futureEvents = events.filter(e => new Date(e.startTime) > now).slice(0, 30); const summary = futureEvents.map(e => `[${e.createdBy.toUpperCase()}] ${e.title} (${e.category}) on ${e.startTime}` ).join('\n'); const prompt = ` Analyze this couple's schedule for the "Cosmic Pulse" dashboard. Events: ${summary} Tasks: 1. Calculate "harmonyScore" (0-100). Higher if balanced mix of work/chores vs dates/fun. Lower if too much work or undefined. 2. "vibe": One word (e.g., Chaotic, Romantic, Busy). 3. "burnoutWarning": If one person has too many 'work'/'chore' items, warn them. Null if fine. 4. "upcomingConflicts": Identify any potential timing clashes. 5. "smartNudges": Suggest 2 nice things they can say/do based on schedule (e.g. "Wish Mom luck on presentation"). Return JSON matching PulseAnalysis interface. `; const fallback: PulseAnalysis = { harmonyScore: 85, vibe: "Stable", burnoutWarning: null, upcomingConflicts: [], smartNudges: ["Hug each other", "Plan a date"] }; try { let result; if (settings.aiProvider === 'ollama') { result = await callOllama(settings, prompt, true); } else { result = await callGemini(prompt, { type: Type.OBJECT, properties: { harmonyScore: { type: Type.NUMBER }, vibe: { type: Type.STRING }, burnoutWarning: { type: Type.STRING, nullable: true }, upcomingConflicts: { type: Type.ARRAY, items: { type: Type.STRING } }, smartNudges: { type: Type.ARRAY, items: { type: Type.STRING } } }, required: ["harmonyScore", "vibe", "smartNudges"] }); } return { ...fallback, ...result }; } catch (e) { console.error("Pulse check failed", e); return fallback; } }; export const interpretDream = async ( dreamText: string, settings: AppSettings ): Promise => { const prompt = ` Interpret this dream in a mystical, positive, and bubbly way. Keep it short (max 2 sentences). Dream: "${dreamText}" Return JSON: { "interpretation": "string" } `; try { let res; if (settings.aiProvider === 'ollama') { res = await callOllama(settings, prompt, true); } else { res = await callGemini(prompt, { type: Type.OBJECT, properties: { interpretation: { type: Type.STRING } } }); } return res.interpretation || "A mysterious vision of the future."; } catch (e) { return "The mists of time obscure this meaning."; } }; export const generateChatResponse = async ( input: string, settings: AppSettings, userRole: string ): Promise<{ text: string; event?: CalendarEvent }> => { const prompt = ` You are a mystical calendar assistant. Current Date: ${new Date().toISOString()} User Role: ${userRole} If request is to create/add/schedule event: Output JSON with "isEvent": true and all event fields. Else: Output JSON with "isEvent": false and "message". User: ${input} `; try { let parsed; if (settings.aiProvider === 'ollama') { parsed = await callOllama(settings, prompt, true); } else { parsed = await callGemini(prompt, { type: Type.OBJECT, properties: { isEvent: { type: Type.BOOLEAN }, message: { type: Type.STRING }, title: { type: Type.STRING }, description: { type: Type.STRING }, startTime: { type: Type.STRING }, endTime: { type: Type.STRING }, category: { type: Type.STRING, enum: ['general', 'chore', 'date', 'work', 'fun', 'magic'] } }, required: ["isEvent"] }); } if (parsed.isEvent) { const newEvent: CalendarEvent = { id: uuidv4(), title: parsed.title || "New Event", description: parsed.description || "", startTime: parsed.startTime || new Date().toISOString(), endTime: parsed.endTime || new Date().toISOString(), category: (parsed.category as EventCategory) || 'general', createdBy: userRole as any }; return { text: parsed.message || `Scheduled "${parsed.title}"!`, event: newEvent }; } else { return { text: parsed.message || "I heard the stars whispering." }; } } catch (e) { return { text: "The connection to the oracle is hazy." }; } }; export const generateDailyHoroscope = async ( momSign: string, dadSign: string, settings: AppSettings ): Promise => { const prompt = ` Generate a mystical, short love horoscope (max 10 words) for signs ${momSign} and ${dadSign}. Return JSON: { "prediction": "string" } `; try { let parsed; if (settings.aiProvider === 'ollama') { parsed = await callOllama(settings, prompt, true); } else { parsed = await callGemini(prompt, { type: Type.OBJECT, properties: { prediction: { type: Type.STRING } } }); } return parsed.prediction || "Love is in the air."; } catch (e) { return "Stars align for you two."; } }; export const analyzeScheduleAndSuggest = async ( events: CalendarEvent[], settings: AppSettings ): Promise => { const eventsSummary = events.slice(0, 20).map(e => `- ${e.title} (${e.category})`).join('\n'); const prompt = ` Analyze this schedule: ${eventsSummary} Suggest 3 fun, specific events for a couple to add. Return JSON: { "suggestions": ["string", "string", "string"] } `; try { let parsed; if (settings.aiProvider === 'ollama') { parsed = await callOllama(settings, prompt, true); } else { parsed = await callGemini(prompt, { type: Type.OBJECT, properties: { suggestions: { type: Type.ARRAY, items: { type: Type.STRING } } } }); } return parsed.suggestions || ["Date Night", "Walk in Park", "Movie Marathon"]; } catch (e) { return ["Plan a date", "Relax together", "Cook dinner"]; } };