diff --git a/assets/css/style.css b/assets/css/style.css index 9d643c4..ed8dcd0 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -274,6 +274,26 @@ body { background: rgba(99, 102, 241, 0.55); } +/* When Gemini API key is not configured */ +.header-gemini-btn.header-btn-no-ai { + opacity: 0.45; + filter: grayscale(0.7); + border-color: rgba(255, 255, 255, 0.25); + background: rgba(255, 255, 255, 0.1); + position: relative; +} +.header-gemini-btn.header-btn-no-ai::after { + content: ''; + position: absolute; + bottom: 2px; + right: 2px; + width: 8px; + height: 8px; + background: #f59e0b; + border-radius: 50%; + border: 1.5px solid rgba(0,0,0,0.3); +} + .gemini-icon { filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2)); } diff --git a/assets/js/app.js b/assets/js/app.js index 4cf74d7..6d303fc 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -80,6 +80,35 @@ function reportError(payload) { // Fires on tab focus and every 5 minutes. const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, ''); +// ── Gemini AI availability ──────────────────────────────────────────────────── +// Set to true in _initApp / syncSettingsFromDB once server confirms key is set. +// All AI entry points call _requireGemini() before opening camera / API calls. +let _geminiAvailable = false; + +function _requireGemini() { + if (_geminiAvailable) return true; + showToast( + '🤖 ' + t('error.no_api_key'), + 'warning', + 6000 + ); + return false; +} + +// Update Gemini button visual state to signal no key configured +function _updateGeminiButtonState() { + const btn = document.querySelector('.header-gemini-btn'); + if (!btn) return; + if (_geminiAvailable) { + btn.classList.remove('header-btn-no-ai'); + btn.removeAttribute('title'); + btn.setAttribute('title', 'Chat con Gemini'); + } else { + btn.classList.add('header-btn-no-ai'); + btn.setAttribute('title', '🤖 Gemini non configurato — imposta GEMINI_API_KEY nelle impostazioni'); + } +} + function _checkWebappUpdate() { const STORAGE_KEY = '_evershelf_update_checked_at'; const SEEN_KEY = '_evershelf_update_seen_ts'; @@ -1779,6 +1808,8 @@ async function syncSettingsFromDB() { try { // Primary: load from server .env const serverSettings = await api('get_settings'); + _geminiAvailable = !!(serverSettings.gemini_key_set || serverSettings.gemini_key); + _updateGeminiButtonState(); const s = getSettings(); const serverKeys = ['default_persons','pref_veloce','pref_pocafame','pref_scadenze', 'pref_healthy','pref_opened','pref_zerowaste','dietary','appliances', @@ -2265,7 +2296,7 @@ function showPage(pageId, param = null) { case 'log': loadLog(); break; case 'ai': initAICamera(); break; case 'settings': loadSettingsUI(); break; - case 'chat': initChat(); break; + case 'chat': if (_requireGemini()) initChat(); break; } // Auto-refresh banner notifications while on dashboard (every 5 min) @@ -2865,6 +2896,7 @@ function openedFraction(item) { } function quickRecipeSuggestion() { + if (!_requireGemini()) return; // Navigate to chat and auto-send a prompt about expiring products showPage('chat'); setTimeout(() => { @@ -7090,6 +7122,7 @@ async function submitUse(e) { // ===== AI IDENTIFICATION ===== async function captureForAI() { + if (!_requireGemini()) return; stopScanner(); showPage('ai'); } @@ -7399,6 +7432,7 @@ async function saveAIProductDirect() { let _pfAiStream = null; async function captureForAIFormFill() { + if (!_requireGemini()) return; document.getElementById('modal-content').innerHTML = `