Cal AI uses the Google Gemini API for all AI-powered features including recipe generation, meal planning, and nutritional analysis.
- API Provider: Google
- Model: Gemini 1.5 Pro
- Base URL:
https://generativelanguage.googleapis.com - Authentication: API Key in Authorization header
- Rate Limit: Depends on your Google Cloud plan
- Go to Google Cloud Console
- Create a new project
- Enable the "Generative Language API"
- Go to Google AI Studio
- Click "Create API Key"
- Copy your API key
Create a file at lib/config/api_keys.dart:
class ApiKeys {
static const String geminiApiKey = 'YOUR_API_KEY_HERE';
}
⚠️ IMPORTANT: This file is in.gitignore. Never commit it to version control!
Endpoint: POST /v1beta/models/gemini-pro:generateContent
Purpose: Generate recipes based on ingredients or preferences
Request Body:
{
"contents": [
{
"parts": [
{
"text": "Generate a detailed recipe for [ingredients] with [dietary filter]. Include ingredients, steps, and estimated nutrition."
}
]
}
]
}Response:
{
"candidates": [
{
"content": {
"parts": [
{
"text": "Recipe details..."
}
]
}
}
]
}Implementation in Cal AI:
// File: lib/services/gemini_api.dart
Future<Recipe> generateRecipe(String ingredients, String dietaryFilter) async {
final prompt = '''
Generate a detailed recipe for the following ingredients: $ingredients
Dietary preference: $dietaryFilter
Format the response as JSON with:
- title: string
- ingredients: array of strings
- steps: array of strings
- nutrition: object with calories, protein, carbs, fat
- cookTime: string
''';
final response = await http.post(
Uri.parse('$baseUrl/v1beta/models/gemini-pro:generateContent?key=$apiKey'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'contents': [{'parts': [{'text': prompt}]}]}),
);
// Parse and return Recipe object
}Purpose: Generate personalized weekly meal plans
Implementation:
Future<MealPlan> generateMealPlan(List<String> preferences) async {
final prompt = '''
Create a 7-day meal plan with these preferences: ${preferences.join(', ')}
Format as JSON with:
- Monday to Sunday: array of recipes
- shoppingList: array of items
- totalCalories: number
''';
// Similar API call structure
}Purpose: Analyze and provide nutritional information for recipes
Implementation:
Future<NutritionInfo> analyzeNutrition(String recipeTitle) async {
final prompt = '''
Analyze the nutritional content of: $recipeTitle
Provide:
- calories
- protein (grams)
- carbohydrates (grams)
- fat (grams)
- fiber (grams)
- vitamins and minerals
''';
// Similar API call structure
}Purpose: Analyze ingredient images to suggest recipes
Implementation:
Future<Recipe> analyzeIngredientImage(List<int> imageBytes) async {
// Convert image to base64
final base64Image = base64Encode(imageBytes);
final response = await http.post(
Uri.parse('$baseUrl/v1beta/models/gemini-pro-vision:generateContent?key=$apiKey'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'contents': [
{
'parts': [
{'text': 'Identify the ingredients in this image and suggest recipes'},
{'inlineData': {'data': base64Image, 'mimeType': 'image/jpeg'}}
]
}
]
}),
);
// Parse response and generate recipe
}https://generativelanguage.googleapis.com/v1beta/models/{model-name}:generateContent?key={API_KEY}
Headers:
- Content-Type: application/json
Body:
{
"contents": [
{
"parts": [
{"text": "Your prompt here"},
// Optional: image data for vision models
]
}
],
"generationConfig": {
"temperature": 0.7,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": 2048
}
}| Parameter | Default | Range | Description |
|---|---|---|---|
temperature |
0.7 | 0.0 - 2.0 | Randomness of responses |
topK |
40 | 1 - 40 | Number of token options to consider |
topP |
0.95 | 0.0 - 1.0 | Nucleus sampling parameter |
maxOutputTokens |
- | 1 - 2048 | Maximum response length |
try {
final recipe = await geminiApiService.generateRecipe(ingredients);
} catch (e) {
if (e.toString().contains('401')) {
// Invalid API key
print('Invalid API key');
} else if (e.toString().contains('429')) {
// Rate limit exceeded
print('Too many requests. Try again later.');
} else if (e.toString().contains('500')) {
// Server error
print('API service error. Try again later.');
} else {
print('Unexpected error: $e');
}
}Edit lib/services/gemini_api.dart:
class GeminiApiService {
static const String baseUrl = 'https://generativelanguage.googleapis.com';
static const String modelName = 'gemini-pro'; // or 'gemini-pro-vision'
// Change these for different API versions
static const String apiVersion = 'v1beta'; // Change to v1 for stable
}Available models:
gemini-pro: Text-based modelgemini-pro-vision: Multimodal (text + images)gemini-1.5-pro: Newer, more powerful model (if available)
static const String modelName = 'gemini-pro-vision'; // For image analysisEdit lib/services/gemini_api.dart:
Map<String, dynamic> generationConfig = {
'temperature': 0.7, // Lower = more deterministic, Higher = more creative
'topK': 40,
'topP': 0.95,
'maxOutputTokens': 2048, // Increase for longer responses
};- Free Tier: Limited requests per day
- Paid Tier: Pay-per-request pricing
- Rate Limits: Check Google AI Pricing
- Prompt Engineering: Write clear, detailed prompts for better results
- Error Handling: Always handle API errors gracefully
- Caching: Cache responses to reduce API calls
- Rate Limiting: Implement delays between requests
- Security:
- Store API key securely
- Never expose key in client-side code in production
- Use backend proxy for sensitive operations
- Cause: Invalid or missing API key
- Solution: Check API key in
lib/config/api_keys.dart
- Cause: Rate limit exceeded
- Solution: Implement request throttling and caching
- Cause: API service issue
- Solution: Retry after a delay, check API status
- Cause: Poor prompt or model limitation
- Solution: Refine your prompt, increase
maxOutputTokens
- Cause: Using wrong model or invalid image format
- Solution: Use
gemini-pro-vision, ensure image is valid JPEG/PNG
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"parts": [{"text": "Write a recipe for pasta"}]
}]
}'void testGeminiApi() async {
final service = GeminiApiService();
try {
final recipe = await service.generateRecipe('tomato, pasta, garlic', 'None');
print('Success: ${recipe.title}');
} catch (e) {
print('Error: $e');
}
}- GETTING_STARTED.md: Setup instructions
- CONFIGURATION.md: Configuration guide
- DEPENDENCIES.md: Package information