Replace scan button in nav with AI recipe generator

- Remove camera/scan button from bottom nav (scan stays in header)
- Add 🍳 Ricetta button with prominent styling in bottom nav
- Auto-detect meal type based on time of day (colazione/pranzo/cena)
- Ask number of persons (default 1)
- Generate recipe via Gemini AI using pantry ingredients
- Prioritize expiring/expired items in recipe suggestions
- Focus on healthy, balanced meals
- Full recipe with ingredients list and step-by-step procedure
- Show prep/cook time, tags, nutrition notes, expiry warnings
- Mark ingredients as from pantry () or extra (🛒)
This commit is contained in:
dadaloop82
2026-03-10 12:12:09 +00:00
parent c015b5b623
commit 0e2287d1e3
4 changed files with 418 additions and 6 deletions
+162
View File
@@ -87,6 +87,10 @@ try {
geminiReadExpiry();
break;
case 'generate_recipe':
generateRecipe($db);
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Unknown action: ' . $action]);
@@ -635,3 +639,161 @@ function geminiReadExpiry(): void {
'raw_text' => $parsed['raw_text'] ?? $text
]);
}
// ===== RECIPE GENERATION WITH GEMINI =====
function generateRecipe(PDO $db): void {
// Load API key from .env
$envFile = __DIR__ . '/../.env';
$apiKey = '';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos($line, '#') === 0) continue;
if (strpos($line, '=') !== false) {
list($key, $val) = explode('=', $line, 2);
if (trim($key) === 'GEMINI_API_KEY') {
$apiKey = trim($val);
}
}
}
}
if (empty($apiKey)) {
echo json_encode(['success' => false, 'error' => 'no_api_key']);
return;
}
$input = json_decode(file_get_contents('php://input'), true);
$mealType = $input['meal'] ?? 'pranzo';
$persons = max(1, intval($input['persons'] ?? 1));
// Fetch all inventory items with expiry info
$stmt = $db->query("
SELECT p.name, p.brand, p.category, i.quantity, p.unit, i.location, i.expiry_date,
CASE WHEN i.expiry_date IS NOT NULL THEN julianday(i.expiry_date) - julianday('now') ELSE 999 END AS days_left
FROM inventory i
JOIN products p ON p.id = i.product_id
WHERE i.quantity > 0
ORDER BY days_left ASC
");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($items)) {
echo json_encode(['success' => false, 'error' => 'La dispensa è vuota!']);
return;
}
// Build ingredient list with expiry info
$ingredientLines = [];
foreach ($items as $item) {
$line = "- {$item['name']}";
if ($item['brand']) $line .= " ({$item['brand']})";
$line .= ": {$item['quantity']} {$item['unit']}";
if ($item['expiry_date']) {
$daysLeft = intval($item['days_left']);
if ($daysLeft < 0) {
$line .= " [SCADUTO da " . abs($daysLeft) . " giorni!]";
} elseif ($daysLeft <= 3) {
$line .= " [SCADE TRA $daysLeft GIORNI - PRIORITÀ ALTA!]";
} elseif ($daysLeft <= 7) {
$line .= " [scade tra $daysLeft giorni - priorità media]";
}
}
$line .= " (in {$item['location']})";
$ingredientLines[] = $line;
}
$ingredientsText = implode("\n", $ingredientLines);
$mealLabels = [
'colazione' => 'colazione (mattina)',
'pranzo' => 'pranzo (mezzogiorno)',
'cena' => 'cena (sera)'
];
$mealLabel = $mealLabels[$mealType] ?? $mealType;
$prompt = <<<PROMPT
Sei un nutrizionista e chef italiano esperto. Genera UNA ricetta per $mealLabel per $persons persona/e usando PRINCIPALMENTE gli ingredienti disponibili nella dispensa dell'utente.
REGOLE IMPORTANTI:
1. PRIORITÀ ASSOLUTA: usa prima gli ingredienti in scadenza o già scaduti (se ancora utilizzabili)
2. Prediligi una ricetta SANA, EQUILIBRATA e NUTRIENTE
3. Usa SOLO ingredienti dalla lista sotto, più al massimo acqua, sale, pepe e olio che si presumono sempre disponibili
4. Adatta le quantità per $persons persona/e
5. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile
6. La ricetta deve essere adatta al pasto: $mealLabel
INGREDIENTI DISPONIBILI IN DISPENSA:
$ingredientsText
Rispondi SOLO con un JSON valido in questo formato esatto (senza markdown, senza backtick):
{
"title": "Nome della ricetta",
"meal": "$mealType",
"persons": $persons,
"prep_time": "tempo preparazione (es. 15 min)",
"cook_time": "tempo cottura (es. 20 min)",
"tags": ["sano", "veloce", "..."],
"expiry_note": "Nota sugli ingredienti in scadenza usati (o stringa vuota)",
"ingredients": [
{"name": "nome ingrediente", "qty": "quantità per $persons persone", "from_pantry": true},
{"name": "sale", "qty": "q.b.", "from_pantry": false}
],
"steps": [
"Passo 1: descrizione dettagliata",
"Passo 2: descrizione dettagliata"
],
"nutrition_note": "Breve nota nutrizionale sulla ricetta"
}
PROMPT;
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={$apiKey}";
$payload = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
],
'generationConfig' => [
'temperature' => 0.7,
'maxOutputTokens' => 2048
]
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode !== 200) {
echo json_encode(['success' => false, 'error' => 'Errore API Gemini', 'http_code' => $httpCode]);
return;
}
$data = json_decode($response, true);
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
// Clean markdown wrapping
$text = preg_replace('/^```json\\s*/i', '', $text);
$text = preg_replace('/\\s*```$/i', '', $text);
$text = trim($text);
$recipe = json_decode($text, true);
if ($recipe && !empty($recipe['title'])) {
echo json_encode(['success' => true, 'recipe' => $recipe]);
} else {
echo json_encode(['success' => false, 'error' => 'Impossibile generare la ricetta', 'raw' => $text]);
}
}