Add Gemini Chat: conversational AI assistant for kitchen help
- Gemini star icon button next to camera in header - Full chat page with message bubbles, typing indicator - Conversation history persisted in localStorage (last 50 messages) - System context includes: full inventory with expiry dates, appliances, dietary restrictions - Multi-turn conversation with Gemini 2.0 Flash - Pre-built suggestion chips: snack, juice/smoothie, light meal, use expiring items - Clear chat button for fresh conversations - Indigo/purple themed UI matching Gemini branding - PHP gemini_chat API endpoint with inventory context injection
This commit is contained in:
+165
@@ -95,6 +95,10 @@ try {
|
||||
geminiIdentifyProduct();
|
||||
break;
|
||||
|
||||
case 'gemini_chat':
|
||||
geminiChat($db);
|
||||
break;
|
||||
|
||||
// ===== BRING! SHOPPING LIST =====
|
||||
case 'bring_list':
|
||||
bringGetList();
|
||||
@@ -807,6 +811,167 @@ function geminiReadExpiry(): void {
|
||||
]);
|
||||
}
|
||||
|
||||
// ===== GEMINI CHAT =====
|
||||
function geminiChat(PDO $db): void {
|
||||
// Load API key
|
||||
$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);
|
||||
$message = $input['message'] ?? '';
|
||||
$history = $input['history'] ?? [];
|
||||
$appliances = $input['appliances'] ?? [];
|
||||
$dietaryRestrictions = $input['dietary_restrictions'] ?? '';
|
||||
|
||||
if (empty($message)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Messaggio vuoto']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch inventory context
|
||||
$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);
|
||||
|
||||
$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]";
|
||||
} elseif ($daysLeft <= 7) {
|
||||
$line .= " [scade tra $daysLeft giorni]";
|
||||
}
|
||||
}
|
||||
$line .= " (in {$item['location']})";
|
||||
$ingredientLines[] = $line;
|
||||
}
|
||||
$ingredientsText = implode("\n", $ingredientLines);
|
||||
|
||||
$appliancesText = '';
|
||||
if (!empty($appliances)) {
|
||||
$appliancesText = "\nElettodomestici disponibili: " . implode(', ', $appliances) . " (più fornelli e forno sempre disponibili).";
|
||||
}
|
||||
|
||||
$dietaryText = '';
|
||||
if (!empty($dietaryRestrictions)) {
|
||||
$dietaryText = "\nRestrizioni alimentari dell'utente: {$dietaryRestrictions}. Rispetta SEMPRE queste restrizioni.";
|
||||
}
|
||||
|
||||
$systemPrompt = <<<PROMPT
|
||||
Sei un assistente cucina italiano esperto, amichevole e conciso. L'utente ha una dispensa e ti chiede consigli su cosa preparare.
|
||||
|
||||
CONTESTO - INGREDIENTI DISPONIBILI IN DISPENSA:
|
||||
{$ingredientsText}
|
||||
{$appliancesText}{$dietaryText}
|
||||
|
||||
REGOLE:
|
||||
1. Rispondi SEMPRE in italiano, in modo colloquiale e amichevole
|
||||
2. Usa SOLO gli ingredienti dalla dispensa dell'utente (più acqua, sale, pepe, olio che si presumono sempre disponibili)
|
||||
3. Dai priorità agli ingredienti in scadenza
|
||||
4. Sii conciso: non fare liste chilometriche, vai al sodo
|
||||
5. Se l'utente chiede una ricetta o preparazione, dai istruzioni chiare con quantità
|
||||
6. Se non ci sono ingredienti adatti per la richiesta, dillo onestamente e suggerisci alternative
|
||||
7. Puoi suggerire combinazioni creative
|
||||
8. Quando menzioni quantità, usa le stesse unità di misura della dispensa
|
||||
9. Ricorda il contesto della conversazione precedente
|
||||
PROMPT;
|
||||
|
||||
// Build conversation for Gemini
|
||||
$contents = [];
|
||||
|
||||
// System instruction as first user+model turn
|
||||
$contents[] = [
|
||||
'role' => 'user',
|
||||
'parts' => [['text' => $systemPrompt]]
|
||||
];
|
||||
$contents[] = [
|
||||
'role' => 'model',
|
||||
'parts' => [['text' => 'Ciao! Sono il tuo assistente cucina. Conosco tutto quello che hai in dispensa e sono pronto ad aiutarti. Cosa ti va di preparare? 😊']]
|
||||
];
|
||||
|
||||
// Add conversation history
|
||||
foreach ($history as $msg) {
|
||||
$role = ($msg['role'] === 'user') ? 'user' : 'model';
|
||||
$contents[] = [
|
||||
'role' => $role,
|
||||
'parts' => [['text' => $msg['text']]]
|
||||
];
|
||||
}
|
||||
|
||||
// Add current message
|
||||
$contents[] = [
|
||||
'role' => 'user',
|
||||
'parts' => [['text' => $message]]
|
||||
];
|
||||
|
||||
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={$apiKey}";
|
||||
|
||||
$payload = [
|
||||
'contents' => $contents,
|
||||
'generationConfig' => [
|
||||
'temperature' => 0.8,
|
||||
'maxOutputTokens' => 1500
|
||||
]
|
||||
];
|
||||
|
||||
$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);
|
||||
$reply = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
||||
|
||||
if (empty($reply)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Risposta vuota da Gemini']);
|
||||
return;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'reply' => $reply]);
|
||||
}
|
||||
|
||||
// ===== RECIPE GENERATION WITH GEMINI =====
|
||||
function generateRecipe(PDO $db): void {
|
||||
// Load API key from .env
|
||||
|
||||
@@ -113,6 +113,30 @@ body {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-gemini-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
font-size: 1.4rem;
|
||||
animation: none;
|
||||
border-width: 2px;
|
||||
background: rgba(99, 102, 241, 0.35);
|
||||
border-color: rgba(199, 210, 254, 0.6);
|
||||
}
|
||||
|
||||
.header-gemini-btn:active {
|
||||
background: rgba(99, 102, 241, 0.55);
|
||||
}
|
||||
|
||||
.gemini-icon {
|
||||
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
|
||||
}
|
||||
|
||||
.header-scan-btn:active {
|
||||
transform: scale(0.9);
|
||||
background: rgba(255,255,255,0.4);
|
||||
@@ -2710,3 +2734,220 @@ body {
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ===== GEMINI CHAT ===== */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--header-height) - var(--nav-height) - 24px);
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
background: white;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
}
|
||||
|
||||
.chat-header-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.btn-chat-clear {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 8px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-chat-clear:active {
|
||||
background: var(--bg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.chat-welcome {
|
||||
text-align: center;
|
||||
padding: 30px 16px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.chat-welcome h3 {
|
||||
margin: 12px 0 6px;
|
||||
color: var(--text);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.chat-welcome p {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
max-width: 300px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
.gemini-icon-lg {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.chat-suggestions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-suggestion {
|
||||
background: white;
|
||||
border: 1.5px solid #e0e7ff;
|
||||
border-radius: 20px;
|
||||
padding: 8px 14px;
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
color: #4338ca;
|
||||
font-weight: 500;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.chat-suggestion:active {
|
||||
background: #e0e7ff;
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
max-width: 85%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 18px;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
animation: chatFadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes chatFadeIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.chat-user {
|
||||
align-self: flex-end;
|
||||
background: #4338ca;
|
||||
color: white;
|
||||
border-bottom-right-radius: 6px;
|
||||
}
|
||||
|
||||
.chat-gemini {
|
||||
align-self: flex-start;
|
||||
background: white;
|
||||
color: var(--text);
|
||||
border-bottom-left-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.chat-gemini strong {
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.chat-gemini ul, .chat-gemini ol {
|
||||
margin: 4px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.chat-gemini li {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.chat-input-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: white;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 24px;
|
||||
padding: 10px 16px;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.chat-input:focus {
|
||||
border-color: #6366f1;
|
||||
}
|
||||
|
||||
.btn-chat-send {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: #4338ca;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-chat-send:active {
|
||||
transform: scale(0.92);
|
||||
background: #3730a3;
|
||||
}
|
||||
|
||||
.btn-chat-send:disabled {
|
||||
background: #a5b4fc;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Typing indicator */
|
||||
.chat-typing {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.chat-typing span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #a5b4fc;
|
||||
border-radius: 50%;
|
||||
animation: chatTyping 1.4s infinite;
|
||||
}
|
||||
|
||||
.chat-typing span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.chat-typing span:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes chatTyping {
|
||||
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
30% { transform: translateY(-6px); opacity: 1; }
|
||||
}
|
||||
|
||||
@@ -521,6 +521,7 @@ function showPage(pageId, param = null) {
|
||||
case 'log': loadLog(); break;
|
||||
case 'ai': initAICamera(); break;
|
||||
case 'settings': loadSettingsUI(); break;
|
||||
case 'chat': initChat(); break;
|
||||
}
|
||||
|
||||
// Stop scanner when leaving scan page
|
||||
@@ -3469,6 +3470,177 @@ async function generateRecipe() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== GEMINI CHAT =====
|
||||
let chatHistory = [];
|
||||
let chatInventoryContext = null;
|
||||
|
||||
function initChat() {
|
||||
// Load chat history from localStorage
|
||||
const saved = localStorage.getItem('gemini_chat_history');
|
||||
if (saved) {
|
||||
try {
|
||||
chatHistory = JSON.parse(saved);
|
||||
renderChatHistory();
|
||||
} catch(e) { chatHistory = []; }
|
||||
}
|
||||
// Pre-load inventory context
|
||||
loadChatContext();
|
||||
// Focus input
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById('chat-input');
|
||||
if (input) input.focus();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
async function loadChatContext() {
|
||||
try {
|
||||
const data = await api('inventory_list');
|
||||
chatInventoryContext = data.inventory || [];
|
||||
} catch(e) { chatInventoryContext = []; }
|
||||
}
|
||||
|
||||
function sendChatSuggestion(text) {
|
||||
document.getElementById('chat-input').value = text;
|
||||
sendChatMessage();
|
||||
}
|
||||
|
||||
async function sendChatMessage() {
|
||||
const input = document.getElementById('chat-input');
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
input.value = '';
|
||||
|
||||
// Hide welcome if first message
|
||||
const welcome = document.querySelector('.chat-welcome');
|
||||
if (welcome) welcome.style.display = 'none';
|
||||
|
||||
// Add user message
|
||||
chatHistory.push({ role: 'user', text });
|
||||
appendChatBubble('user', text);
|
||||
saveChatHistory();
|
||||
|
||||
// Show typing indicator
|
||||
const typingEl = appendChatBubble('gemini', '<div class="chat-typing"><span></span><span></span><span></span></div>', true);
|
||||
scrollChatBottom();
|
||||
|
||||
// Disable send
|
||||
const btn = document.getElementById('btn-chat-send');
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const result = await api('gemini_chat', {}, 'POST', {
|
||||
message: text,
|
||||
history: chatHistory.slice(0, -1).slice(-20), // last 20 messages for context
|
||||
appliances: settings.appliances || [],
|
||||
dietary_restrictions: settings.dietary_restrictions || ''
|
||||
});
|
||||
|
||||
// Remove typing indicator
|
||||
typingEl.remove();
|
||||
|
||||
if (result.success) {
|
||||
chatHistory.push({ role: 'gemini', text: result.reply });
|
||||
appendChatBubble('gemini', formatChatReply(result.reply));
|
||||
} else {
|
||||
const errMsg = result.error === 'no_api_key' ? 'Configura la chiave API Gemini nelle impostazioni.' : (result.error || 'Errore nella risposta');
|
||||
appendChatBubble('gemini', `⚠️ ${escapeHtml(errMsg)}`);
|
||||
}
|
||||
} catch(err) {
|
||||
typingEl.remove();
|
||||
appendChatBubble('gemini', '⚠️ Errore di connessione');
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
saveChatHistory();
|
||||
scrollChatBottom();
|
||||
}
|
||||
|
||||
function appendChatBubble(role, html, isRaw = false) {
|
||||
const container = document.getElementById('chat-messages');
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = `chat-bubble chat-${role}`;
|
||||
if (isRaw) {
|
||||
bubble.innerHTML = html;
|
||||
} else if (role === 'user') {
|
||||
bubble.textContent = html;
|
||||
} else {
|
||||
bubble.innerHTML = html;
|
||||
}
|
||||
container.appendChild(bubble);
|
||||
scrollChatBottom();
|
||||
return bubble;
|
||||
}
|
||||
|
||||
function formatChatReply(text) {
|
||||
// Convert markdown-like formatting
|
||||
let html = escapeHtml(text);
|
||||
// Bold **text**
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
// Italic *text*
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
||||
// Lists
|
||||
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
||||
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
||||
// Numbered lists
|
||||
html = html.replace(/^(\d+)\. (.+)$/gm, '<li>$2</li>');
|
||||
// Line breaks
|
||||
html = html.replace(/\n/g, '<br>');
|
||||
// Clean up consecutive ul tags
|
||||
html = html.replace(/<\/ul>\s*<br>\s*<ul>/g, '');
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderChatHistory() {
|
||||
const container = document.getElementById('chat-messages');
|
||||
if (chatHistory.length === 0) return;
|
||||
|
||||
// Hide welcome
|
||||
const welcome = container.querySelector('.chat-welcome');
|
||||
if (welcome) welcome.style.display = 'none';
|
||||
|
||||
chatHistory.forEach(msg => {
|
||||
if (msg.role === 'user') {
|
||||
appendChatBubble('user', msg.text);
|
||||
} else {
|
||||
appendChatBubble('gemini', formatChatReply(msg.text));
|
||||
}
|
||||
});
|
||||
scrollChatBottom();
|
||||
}
|
||||
|
||||
function scrollChatBottom() {
|
||||
const container = document.getElementById('chat-messages');
|
||||
setTimeout(() => container.scrollTop = container.scrollHeight, 50);
|
||||
}
|
||||
|
||||
function clearChat() {
|
||||
chatHistory = [];
|
||||
localStorage.removeItem('gemini_chat_history');
|
||||
const container = document.getElementById('chat-messages');
|
||||
container.innerHTML = `
|
||||
<div class="chat-welcome">
|
||||
<svg class="gemini-icon-lg" viewBox="0 0 24 24" width="48" height="48" fill="#6366f1"><path d="M12 0C12 6.627 6.627 12 0 12c6.627 0 12 5.373 12 12 0-6.627 5.373-12 12-12-6.627 0-12-5.373-12-12z"/></svg>
|
||||
<h3>Ciao! Sono il tuo assistente cucina</h3>
|
||||
<p>Chiedimi di prepararti un succo, uno spuntino, un piatto veloce... Conosco la tua dispensa, i tuoi elettrodomestici e le tue preferenze!</p>
|
||||
<div class="chat-suggestions">
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Cosa posso preparare per uno spuntino veloce?')">🍿 Spuntino veloce</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Fammi un succo o frullato con quello che ho')">🥤 Succo/Frullato</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Ho fame ma voglio qualcosa di leggero')">🥗 Qualcosa di leggero</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Cosa sta per scadere e come posso usarlo?')">⏰ Usa le scadenze</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
showToast('Chat cancellata', 'success');
|
||||
}
|
||||
|
||||
function saveChatHistory() {
|
||||
// Keep last 50 messages max
|
||||
if (chatHistory.length > 50) chatHistory = chatHistory.slice(-50);
|
||||
localStorage.setItem('gemini_chat_history', JSON.stringify(chatHistory));
|
||||
}
|
||||
|
||||
// ===== INITIALIZATION =====
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
showPage('dashboard');
|
||||
|
||||
Binary file not shown.
+40
-3
@@ -19,9 +19,14 @@
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<h1 class="header-title" onclick="showPage('dashboard')">🏠 Dispensa</h1>
|
||||
<button class="header-scan-btn" onclick="showPage('scan')" title="Scansiona prodotto">
|
||||
📷
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<button class="header-scan-btn header-gemini-btn" onclick="showPage('chat')" title="Chat con Gemini">
|
||||
<svg class="gemini-icon" viewBox="0 0 24 24" width="28" height="28" fill="white"><path d="M12 0C12 6.627 6.627 12 0 12c6.627 0 12 5.373 12 12 0-6.627 5.373-12 12-12-6.627 0-12-5.373-12-12z"/></svg>
|
||||
</button>
|
||||
<button class="header-scan-btn" onclick="showPage('scan')" title="Scansiona prodotto">
|
||||
📷
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -597,6 +602,38 @@
|
||||
<div id="settings-status" class="settings-status" style="display:none"></div>
|
||||
</section>
|
||||
|
||||
<!-- ===== GEMINI CHAT ===== -->
|
||||
<section class="page" id="page-chat">
|
||||
<div class="chat-container">
|
||||
<div class="chat-header-bar">
|
||||
<div class="chat-header-info">
|
||||
<svg class="gemini-icon-sm" viewBox="0 0 24 24" width="22" height="22" fill="#6366f1"><path d="M12 0C12 6.627 6.627 12 0 12c6.627 0 12 5.373 12 12 0-6.627 5.373-12 12-12-6.627 0-12-5.373-12-12z"/></svg>
|
||||
<span class="chat-title">Gemini Chef</span>
|
||||
</div>
|
||||
<button class="btn-chat-clear" onclick="clearChat()" title="Nuova conversazione">🗑️</button>
|
||||
</div>
|
||||
<div class="chat-messages" id="chat-messages">
|
||||
<div class="chat-welcome">
|
||||
<svg class="gemini-icon-lg" viewBox="0 0 24 24" width="48" height="48" fill="#6366f1"><path d="M12 0C12 6.627 6.627 12 0 12c6.627 0 12 5.373 12 12 0-6.627 5.373-12 12-12-6.627 0-12-5.373-12-12z"/></svg>
|
||||
<h3>Ciao! Sono il tuo assistente cucina</h3>
|
||||
<p>Chiedimi di prepararti un succo, uno spuntino, un piatto veloce... Conosco la tua dispensa, i tuoi elettrodomestici e le tue preferenze!</p>
|
||||
<div class="chat-suggestions">
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Cosa posso preparare per uno spuntino veloce?')">🍿 Spuntino veloce</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Fammi un succo o frullato con quello che ho')">🥤 Succo/Frullato</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Ho fame ma voglio qualcosa di leggero')">🥗 Qualcosa di leggero</button>
|
||||
<button class="chat-suggestion" onclick="sendChatSuggestion('Cosa sta per scadere e come posso usarlo?')">⏰ Usa le scadenze</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-input-bar">
|
||||
<input type="text" id="chat-input" class="chat-input" placeholder="Chiedi qualcosa..." onkeydown="if(event.key==='Enter')sendChatMessage()">
|
||||
<button class="btn-chat-send" id="btn-chat-send" onclick="sendChatMessage()">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="white"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
|
||||
Reference in New Issue
Block a user