feat: v1.7.9 — category badges, category search, AI guards
- Category badge on every inventory item (icon + label); 'altro' items refined asynchronously via new guess_category Gemini endpoint (data/category_ai_cache.json) — no AI call when key not configured - Category search: inventory search now matches by macro-category key and translated label (e.g. 'biscotti' finds all cookie items) - Brand fast-path in guessCategoryFromName (Oreo, Barilla, Lavazza…) - Fix: duplicate banner alerts — _bannerLoading guard + _queuedItemIds Set - Fix: mapToLocalCategory with en:dairies (dairi stem added) - Fix: mapToLocalCategory no longer blocks on 'altro' — falls back to guessCategoryFromName(productName) before returning 'altro' - Fix: 'Tonno all'olio' was resolving to condimenti — moved tonno\b check before olio\b in conserve regex block - AI guards: _refineCategoryBadgesAsync and fetchAllPrices now check _geminiAvailable (JS); getShoppingPrice returns no_api_key (PHP) when GEMINI_API_KEY is not set — all AI functions are now explicit
This commit is contained in:
@@ -17,6 +17,7 @@ define('_GH_TK_ENC', '23580718460c2c444031290243627e7971622b29030a3e4d50001e4526
|
||||
define('_GH_TK_KEY', 'D1sp3ns4!Ev3r#26');
|
||||
define('GH_REPO', 'dadaloop82/EverShelf');
|
||||
define('PRICE_CACHE_PATH', __DIR__ . '/../data/shopping_price_cache.json');
|
||||
define('CATEGORY_CACHE_PATH', __DIR__ . '/../data/category_ai_cache.json');
|
||||
|
||||
/** Decode the XOR-obfuscated GitHub token at runtime. */
|
||||
function _ghToken(): string {
|
||||
@@ -99,6 +100,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Ping / heartbeat — early response, no DB or rate-limit required ───────────
|
||||
if (($_GET['action'] ?? '') === 'ping') {
|
||||
echo json_encode(['ok' => true, 'ts' => time()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ===== RATE LIMITING =====
|
||||
/**
|
||||
* Simple file-based rate limiter.
|
||||
@@ -455,6 +462,10 @@ try {
|
||||
getAllShoppingPrices($db);
|
||||
break;
|
||||
|
||||
case 'guess_category':
|
||||
guessCategoryFromAI();
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Unknown action: ' . $action]);
|
||||
@@ -7393,6 +7404,59 @@ PROMPT;
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* GET /api/?action=guess_category&name=...
|
||||
* Returns the macro-category for a product name, using a file cache + Gemini AI fallback.
|
||||
* Response: { category: string }
|
||||
*/
|
||||
function guessCategoryFromAI(): void {
|
||||
$name = trim($_GET['name'] ?? '');
|
||||
if ($name === '') { echo json_encode(['category' => 'altro']); return; }
|
||||
|
||||
// Load cache
|
||||
$cache = [];
|
||||
if (file_exists(CATEGORY_CACHE_PATH)) {
|
||||
$cache = json_decode(file_get_contents(CATEGORY_CACHE_PATH), true) ?? [];
|
||||
}
|
||||
$key = md5(mb_strtolower($name));
|
||||
if (isset($cache[$key])) { echo json_encode(['category' => $cache[$key]]); return; }
|
||||
|
||||
$apiKey = env('GEMINI_API_KEY', '');
|
||||
if ($apiKey === '') { echo json_encode(['category' => 'altro']); return; }
|
||||
|
||||
$cats = 'latticini, carne, pesce, frutta, verdura, pasta, pane, surgelati, bevande, condimenti, snack, conserve, cereali, igiene, pulizia, altro';
|
||||
$prompt = "Sei un classificatore di prodotti alimentari e domestici italiani.\n"
|
||||
. "Classifica il prodotto \"" . addslashes($name) . "\" in UNA di queste categorie esatte: $cats.\n"
|
||||
. "Rispondi con SOLO la parola chiave della categoria, senza spiegazioni né punteggiatura aggiuntiva.";
|
||||
|
||||
$payload = [
|
||||
'contents' => [['parts' => [['text' => $prompt]]]],
|
||||
'generationConfig' => [
|
||||
'temperature' => 0,
|
||||
'maxOutputTokens' => 20,
|
||||
'thinkingConfig' => ['thinkingBudget' => 0],
|
||||
],
|
||||
];
|
||||
|
||||
$result = callGeminiWithFallback($apiKey, $payload, 10);
|
||||
$raw = strtolower(trim($result['data']['candidates'][0]['content']['parts'][0]['text'] ?? ''));
|
||||
$raw = preg_replace('/[^a-z_ ]/', '', $raw);
|
||||
$raw = trim($raw);
|
||||
|
||||
$valid = ['latticini','carne','pesce','frutta','verdura','pasta','pane','surgelati',
|
||||
'bevande','condimenti','snack','conserve','cereali','igiene','pulizia','altro'];
|
||||
$cat = in_array($raw, $valid, true) ? $raw : 'altro';
|
||||
|
||||
// Persist to cache
|
||||
$cache[$key] = $cat;
|
||||
@file_put_contents(CATEGORY_CACHE_PATH, json_encode($cache, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
echo json_encode(['category' => $cat]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* GET /api/?action=get_shopping_price
|
||||
* POST body: { name, quantity, unit, default_quantity, package_unit, country, currency, lang, force_refresh }
|
||||
@@ -7417,6 +7481,12 @@ function getShoppingPrice(PDO $db): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard: price estimation requires Gemini API key
|
||||
if (empty(env('GEMINI_API_KEY'))) {
|
||||
echo json_encode(['success' => false, 'error' => 'no_api_key']);
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = _loadPriceCache();
|
||||
$key = _priceKey($name, $country);
|
||||
$now = time();
|
||||
|
||||
Reference in New Issue
Block a user