Files
my-loves-calendar/services/geminiService.ts
drjones c373bbdab4 first commit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 21:05:27 -08:00

198 lines
6.2 KiB
TypeScript

import { GoogleGenAI, Type } from "@google/genai";
import { EventCategory, CalendarEvent, AppSettings } from "../types";
import { v4 as uuidv4 } from 'uuid';
const apiKey = process.env.API_KEY || '';
const ai = new GoogleGenAI({ apiKey });
// --- Event Parsing ---
export const parseNaturalLanguageEvent = async (
input: string,
referenceDate: Date = new Date()
): Promise<{ title: string; startTime: string; endTime: string; description?: string; category: EventCategory } | null> => {
if (!apiKey) {
console.warn("No API Key found for Gemini");
return null;
}
try {
const model = 'gemini-3-flash-preview';
const prompt = `
Current Date context: ${referenceDate.toISOString()}
User Input: "${input}"
Extract event details.
Categorize into one of: 'general', 'chore', 'date', 'work', 'fun'.
If it sounds romantic, it's a date. If it's cleaning/shopping, it's a chore.
`;
const response = await ai.models.generateContent({
model,
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
startTime: { type: Type.STRING, description: "ISO 8601 format date string" },
endTime: { type: Type.STRING, description: "ISO 8601 format date string" },
description: { type: Type.STRING },
category: { type: Type.STRING, enum: ['general', 'chore', 'date', 'work', 'fun'] }
},
required: ["title", "startTime", "endTime", "category"]
}
}
});
const text = response.text;
if (!text) return null;
return JSON.parse(text);
} catch (error) {
console.error("Error parsing event with Gemini:", error);
return null;
}
};
// --- Chat & Assistant ---
export const generateChatResponse = async (
input: string,
settings: AppSettings,
userRole: string
): Promise<{ text: string; event?: CalendarEvent }> => {
if (!apiKey) return { text: "I need a Gemini API Key to function properly." };
try {
const model = 'gemini-3-flash-preview';
const systemPrompt = `
You are a mystical calendar oracle for a couple.
Current Date: ${new Date().toISOString()}
User Role: ${userRole}
Mom's Sign: ${settings.momZodiac}
Dad's Sign: ${settings.dadZodiac}
If the user asks to create an event, output JSON with "isEvent": true.
Otherwise, output JSON with "isEvent": false and a "message".
Keep messages bubbly, mystical, and concise.
`;
const response = await ai.models.generateContent({
model,
contents: `${systemPrompt}\n\nUser: ${input}`,
config: {
responseMimeType: "application/json",
responseSchema: {
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"]
}
}
});
const parsed = JSON.parse(response.text || "{}");
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 || `I've scheduled "${parsed.title}" for you!`, event: newEvent };
} else {
return { text: parsed.message || "The stars are silent." };
}
} catch (error) {
console.error("Gemini Chat Error", error);
return { text: "The cosmic connection is weak right now." };
}
};
// --- Daily Horoscope ---
export const generateDailyHoroscope = async (
momSign: string,
dadSign: string,
settings: AppSettings
): Promise<string> => {
if (!apiKey) return "Magic requires an API Key.";
try {
const model = 'gemini-3-flash-preview';
const prompt = `
Generate a very short, mystical horoscope prediction (max 10 words) for a couple with signs ${momSign} and ${dadSign} for today.
Focus on their shared energy, love, or domestic life.
`;
const response = await ai.models.generateContent({
model,
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: {
type: Type.OBJECT,
properties: {
prediction: { type: Type.STRING }
}
}
}
});
const parsed = JSON.parse(response.text || "{}");
return parsed.prediction || "Cosmic energy flows through your home.";
} catch (e) {
return "The stars are aligning.";
}
};
// --- Schedule Suggestions ---
export const analyzeScheduleAndSuggest = async (
events: CalendarEvent[],
settings: AppSettings
): Promise<string[]> => {
if (!apiKey) return ["Plan a surprise dinner!", "Go for a moonlit walk.", "Cook a meal together."];
try {
const model = 'gemini-3-flash-preview';
const eventsSummary = events.slice(0, 20).map(e => // Limit to 20 for context window efficiency
`- ${e.title} (${e.category}) on ${e.startTime}`
).join('\n');
const prompt = `
Analyze this couple's schedule:
${eventsSummary}
Based on these events, suggest 3 specific, thoughtful, or fun additional events they should add to balance their life.
Look for missing date nights, overwhelming work blocks, or chores that need doing.
Return 3 short strings.
`;
const response = await ai.models.generateContent({
model,
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: {
type: Type.ARRAY,
items: { type: Type.STRING }
}
}
});
return JSON.parse(response.text || "[]");
} catch (e) {
return ["Plan a date night", "Take a rest day", "Organize the pantry"];
}
};