Move recipes, chat, settings to shared DB — sync across devices, 1 recipe per meal per day
This commit is contained in:
@@ -76,4 +76,46 @@ function migrateDB(PDO $db): void {
|
|||||||
if (!in_array('package_unit', $colNames)) {
|
if (!in_array('package_unit', $colNames)) {
|
||||||
$db->exec("ALTER TABLE products ADD COLUMN package_unit TEXT DEFAULT ''");
|
$db->exec("ALTER TABLE products ADD COLUMN package_unit TEXT DEFAULT ''");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- New shared tables ---
|
||||||
|
// app_settings: key-value store shared across all devices
|
||||||
|
$tables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='app_settings'")->fetchAll();
|
||||||
|
if (empty($tables)) {
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE app_settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL DEFAULT '',
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
// recipes: one per meal per day (last wins)
|
||||||
|
$tables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='recipes'")->fetchAll();
|
||||||
|
if (empty($tables)) {
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE recipes (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
meal TEXT NOT NULL,
|
||||||
|
recipe_json TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(date, meal)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_recipes_date ON recipes(date);
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
// chat_messages: shared chat history
|
||||||
|
$tables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='chat_messages'")->fetchAll();
|
||||||
|
if (empty($tables)) {
|
||||||
|
$db->exec("
|
||||||
|
CREATE TABLE chat_messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
text TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+135
-2
@@ -148,6 +148,32 @@ try {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// ===== SHARED APP DATA =====
|
||||||
|
case 'app_settings_get':
|
||||||
|
appSettingsGet($db);
|
||||||
|
break;
|
||||||
|
case 'app_settings_save':
|
||||||
|
appSettingsSave($db);
|
||||||
|
break;
|
||||||
|
case 'recipes_list':
|
||||||
|
recipesList($db);
|
||||||
|
break;
|
||||||
|
case 'recipes_save':
|
||||||
|
recipesSave($db);
|
||||||
|
break;
|
||||||
|
case 'recipes_delete':
|
||||||
|
recipesDelete($db);
|
||||||
|
break;
|
||||||
|
case 'chat_list':
|
||||||
|
chatList($db);
|
||||||
|
break;
|
||||||
|
case 'chat_save':
|
||||||
|
chatSave($db);
|
||||||
|
break;
|
||||||
|
case 'chat_clear':
|
||||||
|
chatClear($db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Unknown action: ' . $action]);
|
echo json_encode(['error' => 'Unknown action: ' . $action]);
|
||||||
@@ -1197,10 +1223,22 @@ function generateRecipe(PDO $db): void {
|
|||||||
$dietaryText = "\n\nRESTRIZIONI ALIMENTARI:\n{$dietaryRestrictions}\nRispetta SEMPRE queste restrizioni.";
|
$dietaryText = "\n\nRESTRIZIONI ALIMENTARI:\n{$dietaryRestrictions}\nRispetta SEMPRE queste restrizioni.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Today's previous recipes - avoid repetition
|
// Today's previous recipes from DB - avoid repetition
|
||||||
$todayText = '';
|
$todayText = '';
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$todayStmt = $db->prepare("SELECT recipe_json FROM recipes WHERE date = ?");
|
||||||
|
$todayStmt->execute([$today]);
|
||||||
|
$todayDbRecipes = $todayStmt->fetchAll();
|
||||||
|
$todayTitles = [];
|
||||||
|
foreach ($todayDbRecipes as $tr) {
|
||||||
|
$rj = json_decode($tr['recipe_json'], true);
|
||||||
|
if (!empty($rj['title'])) $todayTitles[] = $rj['title'];
|
||||||
|
}
|
||||||
if (!empty($todayRecipes)) {
|
if (!empty($todayRecipes)) {
|
||||||
$todayList = implode(', ', array_map(function($t) { return '"' . $t . '"'; }, $todayRecipes));
|
$todayTitles = array_unique(array_merge($todayTitles, $todayRecipes));
|
||||||
|
}
|
||||||
|
if (!empty($todayTitles)) {
|
||||||
|
$todayList = implode(', ', array_map(function($t) { return '"' . $t . '"'; }, $todayTitles));
|
||||||
$todayText = "\n\nRICETTE GIÀ PREPARATE OGGI:\n{$todayList}\nNON proporre una ricetta simile o con lo stesso concetto di quelle già fatte oggi. Varia il tipo di piatto, gli ingredienti principali e lo stile di cucina. Ad esempio se a pranzo c'era una piadina, a cena proponi pasta, riso, zuppa o altro — MAI un'altra piadina o wrap o piatto concettualmente simile.";
|
$todayText = "\n\nRICETTE GIÀ PREPARATE OGGI:\n{$todayList}\nNON proporre una ricetta simile o con lo stesso concetto di quelle già fatte oggi. Varia il tipo di piatto, gli ingredienti principali e lo stile di cucina. Ad esempio se a pranzo c'era una piadina, a cena proponi pasta, riso, zuppa o altro — MAI un'altra piadina o wrap o piatto concettualmente simile.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2479,3 +2517,98 @@ function formatDupliclickProduct(array $p): array {
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== SHARED APP DATA FUNCTIONS =====
|
||||||
|
|
||||||
|
function appSettingsGet(PDO $db): void {
|
||||||
|
$rows = $db->query("SELECT key, value FROM app_settings")->fetchAll();
|
||||||
|
$settings = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$settings[$row['key']] = json_decode($row['value'], true) ?? $row['value'];
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true, 'settings' => $settings]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appSettingsSave(PDO $db): void {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input || !is_array($input['settings'] ?? null)) {
|
||||||
|
echo json_encode(['error' => 'Missing settings object']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$stmt = $db->prepare("INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at");
|
||||||
|
foreach ($input['settings'] as $key => $value) {
|
||||||
|
$stmt->execute([$key, json_encode($value)]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recipesList(PDO $db): void {
|
||||||
|
$limit = min(intval($_GET['limit'] ?? 60), 200);
|
||||||
|
$rows = $db->query("SELECT id, date, meal, recipe_json, created_at FROM recipes ORDER BY date DESC, created_at DESC LIMIT {$limit}")->fetchAll();
|
||||||
|
$recipes = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$recipes[] = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'date' => $row['date'],
|
||||||
|
'meal' => $row['meal'],
|
||||||
|
'recipe' => json_decode($row['recipe_json'], true),
|
||||||
|
'savedAt' => strtotime($row['created_at']) * 1000
|
||||||
|
];
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true, 'recipes' => $recipes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recipesSave(PDO $db): void {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$date = $input['date'] ?? date('Y-m-d');
|
||||||
|
$meal = $input['meal'] ?? '';
|
||||||
|
$recipe = $input['recipe'] ?? null;
|
||||||
|
|
||||||
|
if (!$meal || !$recipe) {
|
||||||
|
echo json_encode(['error' => 'Missing meal or recipe']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPSERT: one recipe per meal per day (last one wins)
|
||||||
|
$stmt = $db->prepare("INSERT INTO recipes (date, meal, recipe_json, created_at) VALUES (?, ?, ?, datetime('now'))
|
||||||
|
ON CONFLICT(date, meal) DO UPDATE SET recipe_json = excluded.recipe_json, created_at = excluded.created_at");
|
||||||
|
$stmt->execute([$date, $meal, json_encode($recipe)]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => $db->lastInsertId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recipesDelete(PDO $db): void {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = intval($input['id'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
|
$db->prepare("DELETE FROM recipes WHERE id = ?")->execute([$id]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chatList(PDO $db): void {
|
||||||
|
$rows = $db->query("SELECT id, role, text, created_at FROM chat_messages ORDER BY id ASC LIMIT 100")->fetchAll();
|
||||||
|
echo json_encode(['success' => true, 'messages' => $rows]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chatSave(PDO $db): void {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$messages = $input['messages'] ?? [];
|
||||||
|
if (empty($messages)) {
|
||||||
|
echo json_encode(['error' => 'No messages']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$stmt = $db->prepare("INSERT INTO chat_messages (role, text, created_at) VALUES (?, ?, datetime('now'))");
|
||||||
|
foreach ($messages as $msg) {
|
||||||
|
if (!empty($msg['role']) && isset($msg['text'])) {
|
||||||
|
$stmt->execute([$msg['role'], $msg['text']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chatClear(PDO $db): void {
|
||||||
|
$db->exec("DELETE FROM chat_messages");
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
}
|
||||||
|
|||||||
+117
-57
@@ -392,24 +392,77 @@ async function enumerateCameras() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== SETTINGS / CONFIG =====
|
// ===== SETTINGS / CONFIG =====
|
||||||
|
let _settingsCache = null;
|
||||||
|
let _settingsDirty = false;
|
||||||
|
|
||||||
function getSettings() {
|
function getSettings() {
|
||||||
try {
|
if (!_settingsCache) {
|
||||||
const s = JSON.parse(localStorage.getItem('dispensa_settings') || '{}');
|
try {
|
||||||
// Build recipe_prefs array from individual booleans
|
_settingsCache = JSON.parse(localStorage.getItem('dispensa_settings') || '{}');
|
||||||
s.recipe_prefs = [];
|
} catch(e) { _settingsCache = {}; }
|
||||||
if (s.pref_veloce) s.recipe_prefs.push('veloce');
|
}
|
||||||
if (s.pref_pocafame) s.recipe_prefs.push('pocafame');
|
const s = _settingsCache;
|
||||||
if (s.pref_scadenze) s.recipe_prefs.push('scadenze');
|
// Build recipe_prefs array from individual booleans
|
||||||
if (s.pref_healthy) s.recipe_prefs.push('salutare');
|
s.recipe_prefs = [];
|
||||||
if (s.pref_comfort) s.recipe_prefs.push('comfort');
|
if (s.pref_veloce) s.recipe_prefs.push('veloce');
|
||||||
if (s.pref_zerowaste) s.recipe_prefs.push('zerowaste');
|
if (s.pref_pocafame) s.recipe_prefs.push('pocafame');
|
||||||
s.dietary_restrictions = s.dietary || '';
|
if (s.pref_scadenze) s.recipe_prefs.push('scadenze');
|
||||||
return s;
|
if (s.pref_healthy) s.recipe_prefs.push('salutare');
|
||||||
} catch(e) { return {}; }
|
if (s.pref_comfort) s.recipe_prefs.push('comfort');
|
||||||
|
if (s.pref_zerowaste) s.recipe_prefs.push('zerowaste');
|
||||||
|
s.dietary_restrictions = s.dietary || '';
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSettingsToStorage(settings) {
|
function saveSettingsToStorage(settings) {
|
||||||
|
_settingsCache = settings;
|
||||||
localStorage.setItem('dispensa_settings', JSON.stringify(settings));
|
localStorage.setItem('dispensa_settings', JSON.stringify(settings));
|
||||||
|
// Persist to DB
|
||||||
|
_settingsDirty = true;
|
||||||
|
_debouncedSyncSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
const _debouncedSyncSettings = debounce(function() {
|
||||||
|
if (!_settingsDirty) return;
|
||||||
|
_settingsDirty = false;
|
||||||
|
const s = getSettings();
|
||||||
|
// Don't sync secrets or device-specific settings to shared DB
|
||||||
|
const shared = {
|
||||||
|
default_persons: s.default_persons,
|
||||||
|
pref_veloce: s.pref_veloce,
|
||||||
|
pref_pocafame: s.pref_pocafame,
|
||||||
|
pref_scadenze: s.pref_scadenze,
|
||||||
|
pref_healthy: s.pref_healthy,
|
||||||
|
pref_comfort: s.pref_comfort,
|
||||||
|
pref_zerowaste: s.pref_zerowaste,
|
||||||
|
dietary: s.dietary,
|
||||||
|
appliances: s.appliances,
|
||||||
|
spesa_provider: s.spesa_provider,
|
||||||
|
spesa_ai_prompt: s.spesa_ai_prompt
|
||||||
|
};
|
||||||
|
api('app_settings_save', {}, 'POST', { settings: { user_prefs: shared } }).catch(() => {});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
function debounce(fn, ms) {
|
||||||
|
let t; return function(...args) { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncSettingsFromDB() {
|
||||||
|
try {
|
||||||
|
const res = await api('app_settings_get');
|
||||||
|
if (res.success && res.settings && res.settings.user_prefs) {
|
||||||
|
const db = res.settings.user_prefs;
|
||||||
|
const s = getSettings();
|
||||||
|
// Merge DB settings into local (DB wins for shared prefs)
|
||||||
|
for (const key of ['default_persons','pref_veloce','pref_pocafame','pref_scadenze',
|
||||||
|
'pref_healthy','pref_comfort','pref_zerowaste','dietary','appliances',
|
||||||
|
'spesa_provider','spesa_ai_prompt']) {
|
||||||
|
if (db[key] !== undefined) s[key] = db[key];
|
||||||
|
}
|
||||||
|
_settingsCache = s;
|
||||||
|
localStorage.setItem('dispensa_settings', JSON.stringify(s));
|
||||||
|
}
|
||||||
|
} catch(e) { /* offline, use local */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSettingsUI() {
|
async function loadSettingsUI() {
|
||||||
@@ -773,6 +826,8 @@ function setReviewConfirmed(inventoryId) {
|
|||||||
const c = getReviewConfirmed();
|
const c = getReviewConfirmed();
|
||||||
c[inventoryId] = Date.now();
|
c[inventoryId] = Date.now();
|
||||||
localStorage.setItem('review_confirmed', JSON.stringify(c));
|
localStorage.setItem('review_confirmed', JSON.stringify(c));
|
||||||
|
// Also persist to shared DB
|
||||||
|
api('app_settings_save', {}, 'POST', { settings: { review_confirmed: c } }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadReviewItems() {
|
async function loadReviewItems() {
|
||||||
@@ -4027,24 +4082,32 @@ const MEAL_LABELS = {
|
|||||||
'cena': '🌙 Cena'
|
'cena': '🌙 Cena'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== RECIPE ARCHIVE =====
|
// ===== RECIPE ARCHIVE (DB-backed) =====
|
||||||
function getRecipeArchive() {
|
let _recipeArchiveCache = null;
|
||||||
|
|
||||||
|
async function getRecipeArchive() {
|
||||||
|
if (_recipeArchiveCache !== null) return _recipeArchiveCache;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem('dispensa_recipe_archive') || '[]');
|
const res = await api('recipes_list');
|
||||||
} catch { return []; }
|
if (res.success) {
|
||||||
|
_recipeArchiveCache = res.recipes || [];
|
||||||
|
return _recipeArchiveCache;
|
||||||
|
}
|
||||||
|
} catch(e) { console.warn('Failed to load recipes from DB:', e); }
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveRecipeToArchive(recipe) {
|
async function saveRecipeToArchive(recipe) {
|
||||||
const archive = getRecipeArchive();
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
archive.unshift({ date: today, meal: recipe.meal, recipe, savedAt: Date.now() });
|
try {
|
||||||
// Keep max 60 recipes
|
await api('recipes_save', {}, 'POST', { date: today, meal: recipe.meal, recipe });
|
||||||
if (archive.length > 60) archive.length = 60;
|
// Invalidate cache so next load fetches fresh data
|
||||||
localStorage.setItem('dispensa_recipe_archive', JSON.stringify(archive));
|
_recipeArchiveCache = null;
|
||||||
|
} catch(e) { console.error('Failed to save recipe:', e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTodayRecipeTitles() {
|
async function getTodayRecipeTitles() {
|
||||||
const archive = getRecipeArchive();
|
const archive = await getRecipeArchive();
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
return archive
|
return archive
|
||||||
.filter(e => e.date === today && e.recipe && e.recipe.title)
|
.filter(e => e.date === today && e.recipe && e.recipe.title)
|
||||||
@@ -4053,10 +4116,10 @@ function getTodayRecipeTitles() {
|
|||||||
|
|
||||||
let _recipeArchiveEntries = [];
|
let _recipeArchiveEntries = [];
|
||||||
|
|
||||||
function loadRecipeArchive() {
|
async function loadRecipeArchive() {
|
||||||
const container = document.getElementById('recipe-archive');
|
const container = document.getElementById('recipe-archive');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const archive = getRecipeArchive();
|
const archive = await getRecipeArchive();
|
||||||
_recipeArchiveEntries = archive;
|
_recipeArchiveEntries = archive;
|
||||||
|
|
||||||
if (archive.length === 0) {
|
if (archive.length === 0) {
|
||||||
@@ -4119,6 +4182,8 @@ function viewArchivedRecipe(idx) {
|
|||||||
document.getElementById('recipe-result').style.display = '';
|
document.getElementById('recipe-result').style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _cachedRecipe = null;
|
||||||
|
|
||||||
function openRecipeDialog() {
|
function openRecipeDialog() {
|
||||||
const meal = getMealType();
|
const meal = getMealType();
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
@@ -4126,16 +4191,13 @@ function openRecipeDialog() {
|
|||||||
document.getElementById('recipe-overlay').style.display = 'flex';
|
document.getElementById('recipe-overlay').style.display = 'flex';
|
||||||
|
|
||||||
// Check for cached recipe matching current meal type
|
// Check for cached recipe matching current meal type
|
||||||
try {
|
if (_cachedRecipe && _cachedRecipe.meal === meal && _cachedRecipe.recipe) {
|
||||||
const cached = JSON.parse(localStorage.getItem('cachedRecipe') || 'null');
|
document.getElementById('recipe-ask').style.display = 'none';
|
||||||
if (cached && cached.meal === meal && cached.recipe) {
|
document.getElementById('recipe-loading').style.display = 'none';
|
||||||
document.getElementById('recipe-ask').style.display = 'none';
|
renderRecipe(_cachedRecipe.recipe);
|
||||||
document.getElementById('recipe-loading').style.display = 'none';
|
document.getElementById('recipe-result').style.display = '';
|
||||||
renderRecipe(cached.recipe);
|
return;
|
||||||
document.getElementById('recipe-result').style.display = '';
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) { /* ignore parse errors */ }
|
|
||||||
|
|
||||||
// Pre-fill persons from settings
|
// Pre-fill persons from settings
|
||||||
document.getElementById('recipe-persons').value = settings.default_persons || 1;
|
document.getElementById('recipe-persons').value = settings.default_persons || 1;
|
||||||
@@ -4198,13 +4260,9 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
|
|||||||
btn.classList.add('btn-used');
|
btn.classList.add('btn-used');
|
||||||
|
|
||||||
// Persist used state in cached recipe
|
// Persist used state in cached recipe
|
||||||
try {
|
if (_cachedRecipe && _cachedRecipe.recipe && _cachedRecipe.recipe.ingredients && _cachedRecipe.recipe.ingredients[idx]) {
|
||||||
const cached = JSON.parse(localStorage.getItem('cachedRecipe') || 'null');
|
_cachedRecipe.recipe.ingredients[idx].used = true;
|
||||||
if (cached && cached.recipe && cached.recipe.ingredients && cached.recipe.ingredients[idx]) {
|
}
|
||||||
cached.recipe.ingredients[idx].used = true;
|
|
||||||
localStorage.setItem('cachedRecipe', JSON.stringify(cached));
|
|
||||||
}
|
|
||||||
} catch (e) { /* ignore */ }
|
|
||||||
|
|
||||||
showToast('📦 Ingrediente scalato dalla dispensa!', 'success');
|
showToast('📦 Ingrediente scalato dalla dispensa!', 'success');
|
||||||
if (result.added_to_bring) {
|
if (result.added_to_bring) {
|
||||||
@@ -4294,7 +4352,7 @@ function renderRecipe(r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function regenerateRecipe() {
|
function regenerateRecipe() {
|
||||||
localStorage.removeItem('cachedRecipe');
|
_cachedRecipe = null;
|
||||||
document.getElementById('recipe-result').style.display = 'none';
|
document.getElementById('recipe-result').style.display = 'none';
|
||||||
document.getElementById('recipe-loading').style.display = 'none';
|
document.getElementById('recipe-loading').style.display = 'none';
|
||||||
const meal = getMealType();
|
const meal = getMealType();
|
||||||
@@ -4334,7 +4392,7 @@ async function generateRecipe() {
|
|||||||
options,
|
options,
|
||||||
appliances: settings.appliances || [],
|
appliances: settings.appliances || [],
|
||||||
dietary_restrictions: settings.dietary_restrictions || '',
|
dietary_restrictions: settings.dietary_restrictions || '',
|
||||||
today_recipes: getTodayRecipeTitles()
|
today_recipes: await getTodayRecipeTitles()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -4354,8 +4412,8 @@ async function generateRecipe() {
|
|||||||
// Save to archive
|
// Save to archive
|
||||||
saveRecipeToArchive(r);
|
saveRecipeToArchive(r);
|
||||||
|
|
||||||
// Cache the recipe for this meal type
|
// Cache the recipe for this meal type (in-memory only)
|
||||||
localStorage.setItem('cachedRecipe', JSON.stringify({ meal, recipe: r }));
|
_cachedRecipe = { meal, recipe: r };
|
||||||
|
|
||||||
document.getElementById('recipe-loading').style.display = 'none';
|
document.getElementById('recipe-loading').style.display = 'none';
|
||||||
document.getElementById('recipe-result').style.display = '';
|
document.getElementById('recipe-result').style.display = '';
|
||||||
@@ -4373,14 +4431,13 @@ let chatHistory = [];
|
|||||||
let chatInventoryContext = null;
|
let chatInventoryContext = null;
|
||||||
|
|
||||||
function initChat() {
|
function initChat() {
|
||||||
// Load chat history from localStorage
|
// Load chat history from DB
|
||||||
const saved = localStorage.getItem('gemini_chat_history');
|
api('chat_list').then(res => {
|
||||||
if (saved) {
|
if (res.success && res.messages && res.messages.length > 0) {
|
||||||
try {
|
chatHistory = res.messages.map(m => ({ role: m.role, text: m.text }));
|
||||||
chatHistory = JSON.parse(saved);
|
|
||||||
renderChatHistory();
|
renderChatHistory();
|
||||||
} catch(e) { chatHistory = []; }
|
}
|
||||||
}
|
}).catch(() => {});
|
||||||
// Pre-load inventory context
|
// Pre-load inventory context
|
||||||
loadChatContext();
|
loadChatContext();
|
||||||
// Focus input
|
// Focus input
|
||||||
@@ -4515,7 +4572,7 @@ function scrollChatBottom() {
|
|||||||
|
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
chatHistory = [];
|
chatHistory = [];
|
||||||
localStorage.removeItem('gemini_chat_history');
|
api('chat_clear', {}, 'POST').catch(() => {});
|
||||||
const container = document.getElementById('chat-messages');
|
const container = document.getElementById('chat-messages');
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="chat-welcome">
|
<div class="chat-welcome">
|
||||||
@@ -4536,11 +4593,14 @@ function clearChat() {
|
|||||||
function saveChatHistory() {
|
function saveChatHistory() {
|
||||||
// Keep last 50 messages max
|
// Keep last 50 messages max
|
||||||
if (chatHistory.length > 50) chatHistory = chatHistory.slice(-50);
|
if (chatHistory.length > 50) chatHistory = chatHistory.slice(-50);
|
||||||
localStorage.setItem('gemini_chat_history', JSON.stringify(chatHistory));
|
// Save last 2 messages (the newest pair) to DB
|
||||||
|
const newMsgs = chatHistory.slice(-2);
|
||||||
|
api('chat_save', {}, 'POST', { messages: newMsgs }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== INITIALIZATION =====
|
// ===== INITIALIZATION =====
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
syncSettingsFromDB();
|
||||||
showPage('dashboard');
|
showPage('dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -858,6 +858,6 @@
|
|||||||
<div class="modal-content" id="modal-content" onclick="event.stopPropagation()"></div>
|
<div class="modal-content" id="modal-content" onclick="event.stopPropagation()"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="assets/js/app.js?v=20260312x"></script>
|
<script src="assets/js/app.js?v=20260312y"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user