198 lines
6.2 KiB
TypeScript
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"];
|
|
}
|
|
}; |