FlixFinder / services /geminiService.ts
KingCam326's picture
Upload 21 files
9aaa179 verified
// Fix: This file was previously placeholder text. It is now fully implemented
// to provide the required services for fetching data from the Gemini API,
// resolving all module-related errors.
import { GoogleGenAI, Type } from "@google/genai";
import { MediaType, Recommendation } from "../types";
// Initialize the Google Gemini AI client.
// The API key is sourced from the environment variables, as per guidelines.
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY! });
/**
* Fetches a list of popular titles for a given media type and genres.
* @param mediaType - The type of media (Movies, TV Shows, Video Games).
* @param genres - An array of selected genres.
* @param isGamePassOnly - Optional flag for video games to filter by Game Pass availability.
* @param isMultiplayerOnly - Optional flag for video games to filter by multiplayer availability.
* @returns A promise that resolves to an array of title strings.
*/
export const fetchTitles = async (
mediaType: MediaType,
genres: string[],
isGamePassOnly?: boolean,
isMultiplayerOnly?: boolean
): Promise<string[]> => {
const gamePassQuery = isGamePassOnly ? " that are available on PC or Console Game Pass" : "";
const multiplayerQuery = isMultiplayerOnly ? " multiplayer" : "";
const prompt = `
List 20 popular and highly-rated${multiplayerQuery} ${mediaType}${gamePassQuery} from the following genres: ${genres.join(', ')}.
Focus on a diverse mix of classic and recent titles.
Return ONLY a JSON array of strings, where each string is a title. Do not include any other text or explanation.
`;
try {
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: prompt,
config: {
responseMimeType: 'application/json',
responseSchema: {
type: Type.ARRAY,
items: {
type: Type.STRING,
description: "The title of the media.",
}
}
}
});
const jsonString = response.text.trim();
// The response is expected to be a JSON string array.
return JSON.parse(jsonString);
} catch (e) {
console.error("Error fetching titles from Gemini API:", e);
// Re-throw a more user-friendly error to be caught by the UI layer.
throw new Error("Failed to fetch title suggestions from the AI. Please try again.");
}
};
/**
* Fetches personalized recommendations based on user's favorite titles.
* @param mediaType - The type of media (Movies, TV Shows, Video Games).
* @param titles - An array of titles the user likes.
* @param isGamePassOnly - Optional flag for video games to filter by Game Pass availability.
* @param isMultiplayerOnly - Optional flag for video games to filter by multiplayer availability.
* @returns A promise that resolves to an array of Recommendation objects.
*/
export const fetchRecommendations = async (
mediaType: MediaType,
titles: string[],
isGamePassOnly?: boolean,
isMultiplayerOnly?: boolean,
): Promise<Recommendation[]> => {
const gamePassQuery = isGamePassOnly ? " that are available on PC or Console Game Pass" : "";
const multiplayerQuery = isMultiplayerOnly ? " multiplayer" : "";
const isGames = mediaType === MediaType.VideoGames;
const isStreamable = mediaType === MediaType.Movies || mediaType === MediaType.TVShows;
const playerCountDescription = isGames
? "Include an estimated 'playerCount' for recent peak concurrent players and a 'livePlayerCount' for the current live players. These should both be numbers."
: "";
const streamingDescription = isStreamable
? "- streamingOn: A list of major streaming services where it's available (e.g., Netflix, Hulu, Disney+)."
: "";
// Base properties for the JSON schema for all media types.
const properties: { [key: string]: object } = {
title: { type: Type.STRING, description: 'The title of the recommended item.' },
synopsis: { type: Type.STRING, description: 'A brief, engaging synopsis (2-3 sentences).' },
genres: { type: Type.ARRAY, items: { type: Type.STRING }, description: 'A list of relevant genres.' },
rating: { type: Type.NUMBER, description: 'An estimated rating out of 10 (e.g., 8.5).' },
};
// Add a game-specific property to the schema if the media type is Video Games.
if (isGames) {
properties.playerCount = { type: Type.NUMBER, description: 'Estimated recent peak concurrent player count.' };
properties.livePlayerCount = { type: Type.NUMBER, description: 'Estimated current live player count.' };
}
if (isStreamable) {
properties.streamingOn = { type: Type.ARRAY, items: { type: Type.STRING }, description: 'List of streaming services.' };
}
const prompt = `
Based on a user's enjoyment of the following ${mediaType}: ${titles.join(', ')}.
Please recommend 5 other${multiplayerQuery} ${mediaType}${gamePassQuery} they might like.
For each recommendation, provide the following details:
- title: The title of the recommendation.
- synopsis: A brief, 2-3 sentence summary.
- genres: A list of relevant genres.
- rating: An estimated critical and audience rating out of 10 (e.g., 8.5).
${streamingDescription}
${playerCountDescription}
Return ONLY a JSON array of objects matching the defined schema. Do not include any other text, markdown, or explanation.
`;
try {
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: prompt,
config: {
responseMimeType: 'application/json',
responseSchema: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: properties,
required: ['title', 'synopsis', 'genres', 'rating']
}
}
}
});
const jsonString = response.text.trim();
// The response is expected to be a JSON string representing an array of Recommendation objects.
return JSON.parse(jsonString);
} catch (e) {
console.error("Error fetching recommendations from Gemini API:", e);
// Re-throw a more user-friendly error to be caught by the UI layer.
throw new Error("Failed to fetch recommendations from the AI. Please try again.");
}
};