diff --git a/api/index.php b/api/index.php index 9eae095..a9bc7c0 100644 --- a/api/index.php +++ b/api/index.php @@ -18,6 +18,9 @@ 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'); +define('SHELF_CACHE_PATH', __DIR__ . '/../data/opened_shelf_cache.json'); +define('FOODFACTS_CACHE_PATH',__DIR__ . '/../data/food_facts_cache.json'); +define('BRING_TOKEN_PATH', __DIR__ . '/../data/bring_token.json'); define('AI_USAGE_PATH', __DIR__ . '/../data/ai_usage.json'); // Gemini pricing (USD per 1M tokens) — overridable via .env // gemini-2.5-flash: $0.15 input / $0.60 output @@ -146,49 +149,189 @@ if (($_GET['action'] ?? '') === 'get_logs') { // ── Gemini token usage + cost estimate ──────────────────────────────────────── if (($_GET['action'] ?? '') === 'gemini_usage') { header('Content-Type: application/json; charset=utf-8'); - $data = file_exists(AI_USAGE_PATH) ? (json_decode(file_get_contents(AI_USAGE_PATH), true) ?: []) : []; - $month = date('Y-m'); - $cur = $data[$month] ?? ['input_tokens' => 0, 'output_tokens' => 0, 'calls' => 0, 'by_action' => [], 'by_model' => []]; - // Per-model cost calculation - $totalCost = 0.0; - foreach (($cur['by_model'] ?? []) as $mdl => $mu) { - $inRate = str_contains($mdl, '2.5') ? (float)(env('GEMINI_COST_INPUT_PER_1M') ?: GEMINI_COST_25F_IN) : (float)(env('GEMINI_COST_INPUT_PER_1M') ?: GEMINI_COST_20F_IN); - $outRate = str_contains($mdl, '2.5') ? (float)(env('GEMINI_COST_OUTPUT_PER_1M') ?: GEMINI_COST_25F_OUT) : (float)(env('GEMINI_COST_OUTPUT_PER_1M') ?: GEMINI_COST_20F_OUT); - $totalCost += ($mu['in'] / 1_000_000) * $inRate + ($mu['out'] / 1_000_000) * $outRate; - } - // Fallback if by_model not populated (old data) - if ($totalCost === 0.0 && ($cur['input_tokens'] > 0 || $cur['output_tokens'] > 0)) { - $inRate = (float)(env('GEMINI_COST_INPUT_PER_1M') ?: GEMINI_COST_25F_IN); - $outRate = (float)(env('GEMINI_COST_OUTPUT_PER_1M') ?: GEMINI_COST_25F_OUT); - $totalCost = ($cur['input_tokens'] / 1_000_000) * $inRate + ($cur['output_tokens'] / 1_000_000) * $outRate; + // ── Cost helper ─────────────────────────────────────────────────────────── + $calcCost = function(array $bucket): float { + $cost = 0.0; + foreach (($bucket['by_model'] ?? []) as $mdl => $mu) { + $inRate = str_contains($mdl, '2.5') ? GEMINI_COST_25F_IN : GEMINI_COST_20F_IN; + $outRate = str_contains($mdl, '2.5') ? GEMINI_COST_25F_OUT : GEMINI_COST_20F_OUT; + $cost += ($mu['in'] / 1_000_000) * $inRate + ($mu['out'] / 1_000_000) * $outRate; + } + if ($cost === 0.0 && ($bucket['input_tokens'] ?? 0) > 0) { + $cost = ($bucket['input_tokens'] / 1_000_000) * GEMINI_COST_25F_IN + + ($bucket['output_tokens'] ?? 0) / 1_000_000 * GEMINI_COST_25F_OUT; + } + return round($cost, 6); + }; + + // ── Tracked usage (ai_usage.json) ──────────────────────────────────────── + $aiData = file_exists(AI_USAGE_PATH) ? (json_decode(file_get_contents(AI_USAGE_PATH), true) ?: []) : []; + $month = date('Y-m'); + $year = date('Y'); + $cur = $aiData[$month] ?? ['input_tokens' => 0, 'output_tokens' => 0, 'calls' => 0, 'by_action' => [], 'by_model' => []]; + + // Yearly totals (sum all months of current year) + $yearBucket = ['input_tokens' => 0, 'output_tokens' => 0, 'calls' => 0, 'by_model' => []]; + foreach ($aiData as $k => $v) { + if (!str_starts_with($k, $year)) continue; + $yearBucket['input_tokens'] += (int)($v['input_tokens'] ?? 0); + $yearBucket['output_tokens'] += (int)($v['output_tokens'] ?? 0); + $yearBucket['calls'] += (int)($v['calls'] ?? 0); + foreach (($v['by_model'] ?? []) as $mdl => $mu) { + if (!isset($yearBucket['by_model'][$mdl])) { + $yearBucket['by_model'][$mdl] = ['in' => 0, 'out' => 0, 'calls' => 0]; + } + $yearBucket['by_model'][$mdl]['in'] += $mu['in'] ?? 0; + $yearBucket['by_model'][$mdl]['out'] += $mu['out'] ?? 0; + $yearBucket['by_model'][$mdl]['calls'] += $mu['calls'] ?? 0; + } } - // Log sizes — EverLog::listFiles() returns [{file, size_kb, mtime}, ...] + // ── Retroactive estimate (from cache files — before tracking started) ──── + // Token averages per action type (empirical estimates): + // price lookup : ~350 in + ~125 out + // category : ~200 in + ~30 out + // shelf life : ~500 in + ~80 out + $monthStart = mktime(0, 0, 0, (int)date('m'), 1, (int)date('Y')); + $yearStart = mktime(0, 0, 0, 1, 1, (int)date('Y')); + + $retroMonthCalls = 0; $retroYearCalls = 0; + $retroMonthIn = 0; $retroYearIn = 0; + $retroMonthOut = 0; $retroYearOut = 0; + + // Price cache + $priceCache = file_exists(PRICE_CACHE_PATH) + ? (json_decode(file_get_contents(PRICE_CACHE_PATH), true) ?: []) : []; + foreach ($priceCache as $v) { + if (!is_array($v) || !isset($v['cached_at'])) continue; + if ($v['cached_at'] >= $monthStart) { $retroMonthCalls++; $retroMonthIn += 350; $retroMonthOut += 125; } + if ($v['cached_at'] >= $yearStart) { $retroYearCalls++; $retroYearIn += 350; $retroYearOut += 125; } + } + // Shelf-life AI cache (source='ai' only) + $shelfCache = file_exists(SHELF_CACHE_PATH) + ? (json_decode(file_get_contents(SHELF_CACHE_PATH), true) ?: []) : []; + foreach ($shelfCache as $v) { + if (!is_array($v) || ($v['source'] ?? '') !== 'ai') continue; + if (($v['ts'] ?? 0) >= $monthStart) { $retroMonthCalls++; $retroMonthIn += 500; $retroMonthOut += 80; } + if (($v['ts'] ?? 0) >= $yearStart) { $retroYearCalls++; $retroYearIn += 500; $retroYearOut += 80; } + } + // Category AI cache (no timestamps — count as year only) + $catCache = file_exists(CATEGORY_CACHE_PATH) + ? (json_decode(file_get_contents(CATEGORY_CACHE_PATH), true) ?: []) : []; + $catTotal = count($catCache); + $retroYearCalls += $catTotal; $retroYearIn += $catTotal * 200; $retroYearOut += $catTotal * 30; + + $retroMonthCostUsd = round(($retroMonthIn / 1_000_000) * GEMINI_COST_25F_IN + + ($retroMonthOut / 1_000_000) * GEMINI_COST_25F_OUT, 6); + $retroYearCostUsd = round(($retroYearIn / 1_000_000) * GEMINI_COST_25F_IN + + ($retroYearOut / 1_000_000) * GEMINI_COST_25F_OUT, 6); + + // Only expose retro if there is actual tracked data gap (calls tracked < retro estimate) + $hasRetro = ($retroMonthCalls > $cur['calls']); + + // ── DB stats ────────────────────────────────────────────────────────────── + $dbStats = []; + try { + $db = getDB(); + $row = $db->query("SELECT + (SELECT COUNT(*) FROM products) as products_total, + (SELECT COUNT(*) FROM inventory WHERE quantity > 0) as inventory_active, + (SELECT COUNT(*) FROM transactions WHERE undone=0 AND created_at >= date('now','start of month')) as tx_month, + (SELECT COUNT(*) FROM transactions WHERE undone=0 AND created_at >= date('now','start of year')) as tx_year, + (SELECT COUNT(*) FROM transactions WHERE type='in' AND undone=0 AND created_at >= date('now','start of month')) as restock_month, + (SELECT COUNT(*) FROM transactions WHERE type IN ('out','waste') AND undone=0 AND created_at >= date('now','start of month')) as use_month, + (SELECT COUNT(*) FROM products WHERE created_at >= date('now','start of month')) as products_month, + (SELECT COUNT(CASE WHEN expiry_date < date('now') AND quantity > 0 THEN 1 END) FROM inventory) as expired, + (SELECT COUNT(CASE WHEN expiry_date BETWEEN date('now') AND date('now','+7 days') AND quantity > 0 THEN 1 END) FROM inventory) as expiring_soon, + (SELECT COUNT(CASE WHEN quantity = 0 THEN 1 END) FROM inventory) as finished + ")->fetch(PDO::FETCH_ASSOC); + $dbStats = $row ?: []; + } catch (Throwable $e) { /* ignore */ } + + // ── Log info ────────────────────────────────────────────────────────────── $logFilesInfo = EverLog::listFiles(); $logBytes = 0; foreach ($logFilesInfo as $lf) { $logBytes += (int)(($lf['size_kb'] ?? 0) * 1024); } + // ── Backup info ─────────────────────────────────────────────────────────── + $backupDir = dirname(__DIR__) . '/data/backups'; + $backupFiles = is_dir($backupDir) ? (glob($backupDir . '/*.db') ?: []) : []; + rsort($backupFiles); + $lastBackupTs = $backupFiles ? (int)filemtime($backupFiles[0]) : 0; + $lastBackupBytes = $backupFiles ? (int)filesize($backupFiles[0]) : 0; + + // ── Bring! token expiry ─────────────────────────────────────────────────── + $bringToken = file_exists(BRING_TOKEN_PATH) + ? (json_decode(file_get_contents(BRING_TOKEN_PATH), true) ?: []) : []; $bringExpiresTs = (int)($bringToken['expires'] ?? 0); + echo json_encode([ - 'month' => $month, - 'input_tokens' => (int)$cur['input_tokens'], - 'output_tokens' => (int)$cur['output_tokens'], - 'calls' => (int)$cur['calls'], - 'by_action' => $cur['by_action'] ?? [], - 'by_model' => $cur['by_model'] ?? [], - 'cost_usd' => round($totalCost, 6), - 'log_bytes' => $logBytes, - 'log_level' => EverLog::levelName(), - 'log_files' => count($logFilesInfo), - 'db_bytes' => file_exists(DB_PATH) ? filesize(DB_PATH) : 0, - 'history' => array_map(fn($k, $v) => [ + 'month' => $month, + 'year' => $year, + + // Tracked (ai_usage.json — since tracking start) + 'tracked' => [ + 'calls' => (int)$cur['calls'], + 'input_tokens' => (int)$cur['input_tokens'], + 'output_tokens' => (int)$cur['output_tokens'], + 'cost_usd' => $calcCost($cur), + 'by_action' => $cur['by_action'] ?? [], + 'by_model' => $cur['by_model'] ?? [], + ], + + // Yearly tracked totals + 'year_tracked' => [ + 'calls' => (int)$yearBucket['calls'], + 'input_tokens' => (int)$yearBucket['input_tokens'], + 'output_tokens' => (int)$yearBucket['output_tokens'], + 'cost_usd' => $calcCost($yearBucket), + ], + + // Retroactive estimate from cache files (before tracking started) + 'retro' => $hasRetro ? [ + 'calls_month' => $retroMonthCalls, + 'calls_year' => $retroYearCalls, + 'tok_in_month' => $retroMonthIn, + 'tok_out_month'=> $retroMonthOut, + 'tok_in_year' => $retroYearIn, + 'tok_out_year' => $retroYearOut, + 'cost_month' => $retroMonthCostUsd, + 'cost_year' => $retroYearCostUsd, + ] : null, + + // DB activity + 'db' => array_merge( + array_map('intval', $dbStats), + ['bytes' => file_exists(DB_PATH) ? (int)filesize(DB_PATH) : 0] + ), + + // Cache sizes + 'caches' => [ + 'price' => count($priceCache), + 'shelf' => count($shelfCache), + 'category' => $catTotal, + 'foodfacts' => count(file_exists(FOODFACTS_CACHE_PATH) + ? (json_decode(file_get_contents(FOODFACTS_CACHE_PATH), true) ?: []) : []), + ], + + // System + 'log_bytes' => $logBytes, + 'log_level' => EverLog::levelName(), + 'log_files' => count($logFilesInfo), + 'last_backup_ts' => $lastBackupTs, + 'last_backup_bytes' => $lastBackupBytes, + 'bring_expires_ts' => $bringExpiresTs, + + // History (last 12 months) + 'history' => array_map(fn($k, $v) => [ 'month' => $k, 'input_tokens' => (int)($v['input_tokens'] ?? 0), 'output_tokens' => (int)($v['output_tokens'] ?? 0), 'calls' => (int)($v['calls'] ?? 0), - ], array_keys($data), array_values($data)), + 'cost_usd' => $calcCost($v), + ], array_keys($aiData), array_values($aiData)), ], JSON_UNESCAPED_UNICODE); exit; } diff --git a/assets/js/app.js b/assets/js/app.js index df4602a..4819d71 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2225,79 +2225,169 @@ async function _renderInfoTab() { try { const d = await api('gemini_usage'); const s = getSettings(); - const sym = s.price_currency === 'USD' ? '$' : (s.price_currency === 'GBP' ? '£' : '€'); + + // ── Locale & helpers ───────────────────────────────────────────────── + const langMap = {it:'it-IT', en:'en-US', de:'de-DE', fr:'fr-FR', es:'es-ES'}; + const locale = langMap[s.language] || langMap[navigator.language?.slice(0,2)] || 'it-IT'; + const [yr, mo] = (d.month || '').split('-'); + const monthLabel = new Intl.DateTimeFormat(locale, {month:'long', year:'numeric'}) + .format(new Date(parseInt(yr), parseInt(mo)-1, 1)); + + // Cost → display currency + const toCurr = (usd) => { + if (!usd) return '—'; + const c = s.price_currency; + const v = c === 'EUR' ? usd * 0.92 : c === 'GBP' ? usd * 0.79 : usd; + const sym = c === 'EUR' ? '€' : c === 'GBP' ? '£' : '$'; + return sym + v.toFixed(4); + }; + const fmtTok = n => n >= 1_000_000 ? (n/1_000_000).toFixed(2)+'M' + : n >= 1_000 ? Math.round(n/1_000)+'K' : String(n||0); + const fmtBytes = b => b > 1048576 ? (b/1048576).toFixed(1)+' MB' + : b > 1024 ? Math.round(b/1024)+' KB' : (b||0)+' B'; + const fmtDate = ts => ts ? new Intl.DateTimeFormat(locale, {day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit'}).format(new Date(ts*1000)) : '—'; + const pill = (val, label, color='') => + `
+
${val}
+
${label}
+
`; // ── AI Usage card ──────────────────────────────────────────────────── if (aiEl) { - const totalTok = (d.input_tokens || 0) + (d.output_tokens || 0); - const costUsd = d.cost_usd || 0; + const tr = d.tracked || {}; + const retro = d.retro; + const yr_t = d.year_tracked || {}; - // Convert cost to display currency (rough fixed rates) - let costDisplay = '$' + costUsd.toFixed(4); - if (s.price_currency === 'EUR') costDisplay = '€' + (costUsd * 0.92).toFixed(4); - if (s.price_currency === 'GBP') costDisplay = '£' + (costUsd * 0.79).toFixed(4); + // Update card subtitle dynamically + const hintEl = aiEl.closest('.settings-card')?.querySelector('.info-ai-subtitle'); + if (hintEl) hintEl.textContent = t('settings.info.ai_overview').replace('{month}', monthLabel); - // By-action breakdown - const actions = d.by_action || {}; - const actionRows = Object.entries(actions) - .sort((a, b) => b[1] - a[1]) - .map(([k, v]) => `${k}${v} calls`) - .join(''); + // Tracked section + let trackedHtml = ''; + if (tr.calls > 0) { + const actionRows = Object.entries(tr.by_action || {}) + .sort((a,b) => b[1]-a[1]).slice(0, 8) + .map(([k,v]) => `${k}${v} ${t('settings.info.calls_unit')}`).join(''); + const modelRows = Object.entries(tr.by_model || {}) + .map(([m,mv]) => `${m}${fmtTok((mv.in||0)+(mv.out||0))}`).join(''); + trackedHtml = ` +
+
${t('settings.info.tracked_section')}
+
+ ${pill(tr.calls, t('settings.info.ai_calls'))} + ${pill(fmtTok((tr.input_tokens||0)+(tr.output_tokens||0)), t('settings.info.total_tokens'))} + ${pill(toCurr(tr.cost_usd), t('settings.info.est_cost'), '#15803d')} +
+ ${actionRows ? `
${t('settings.info.by_action')}${actionRows}
` : ''} + ${modelRows ? `
${t('settings.info.by_model')}${modelRows}
` : ''} +
`; + } - // By-model breakdown - const models = d.by_model || {}; - const modelRows = Object.entries(models) - .map(([m, mv]) => `${m}${((mv.in||0)+(mv.out||0)).toLocaleString()} tok`) - .join(''); + // Retroactive estimate section + let retroHtml = ''; + if (retro && retro.calls_month > 0) { + retroHtml = ` +
+
+ ${t('settings.info.retro_section').replace('{month}', monthLabel)} +
+
+ ${pill('~'+retro.calls_month, t('settings.info.ai_calls'))} + ${pill('~'+fmtTok((retro.tok_in_month||0)+(retro.tok_out_month||0)), t('settings.info.total_tokens'))} + ${pill('~'+toCurr(retro.cost_month), t('settings.info.est_cost'), '#92400e')} +
+

${t('settings.info.retro_note')}

+
`; + } - aiEl.innerHTML = ` -
-
-
${totalTok.toLocaleString()}
-
${t('settings.info.total_tokens')}
+ // Yearly section + const yearTotCalls = (yr_t.calls||0) + (retro?.calls_year||0); + const yearTotTokIn = (yr_t.input_tokens||0) + (retro?.tok_in_year||0); + const yearTotTokOut = (yr_t.output_tokens||0) + (retro?.tok_out_year||0); + const yearTotCost = (yr_t.cost_usd||0) + (retro?.cost_year||0); + const yearHtml = ` +
+
${t('settings.info.year_section').replace('{year}', d.year)}
+
+ ${pill('~'+yearTotCalls, t('settings.info.ai_calls'))} + ${pill('~'+fmtTok(yearTotTokIn+yearTotTokOut), t('settings.info.total_tokens'))} + ${pill('~'+toCurr(yearTotCost), t('settings.info.est_cost'), '#15803d')}
-
-
${costDisplay}
-
${t('settings.info.est_cost')} (${d.month})
-
-
-
- ↑ ${t('settings.info.input_tok')}: ${(d.input_tokens||0).toLocaleString()} - ↓ ${t('settings.info.output_tok')}: ${(d.output_tokens||0).toLocaleString()} - ${t('settings.info.ai_calls')}: ${d.calls||0} -
- ${actionRows ? `
${t('settings.info.by_action')}${actionRows}
` : ''} - ${modelRows ? `
${t('settings.info.by_model')}${modelRows}
` : ''} -

${t('settings.info.pricing_note')}

- `; +
`; + + aiEl.innerHTML = trackedHtml + retroHtml + yearHtml + + `

${t('settings.info.pricing_note')}

`; + } + + // ── Inventory card ─────────────────────────────────────────────────── + const invEl = document.getElementById('info-inv-content'); + if (invEl && d.db) { + const db = d.db; + const expColor = db.expired > 0 ? '#dc2626' : ''; + const soonColor = db.expiring_soon > 0 ? '#d97706' : ''; + invEl.innerHTML = ` +
+ ${pill(db.inventory_active, t('settings.info.inv_active'))} + ${pill(db.products_total, t('settings.info.inv_products'))} + ${pill(db.expiring_soon, t('settings.info.inv_expiring'), soonColor)} + ${pill(db.expired, t('settings.info.inv_expired'), expColor)} + ${pill(db.finished, t('settings.info.inv_finished'))} +
`; + } + + // ── Activity card ──────────────────────────────────────────────────── + const actEl = document.getElementById('info-act-content'); + if (actEl && d.db) { + const db = d.db; + actEl.innerHTML = ` +
+ ${pill(db.tx_month, t('settings.info.act_tx_month'))} + ${pill(db.restock_month, t('settings.info.act_restock'))} + ${pill(db.use_month, t('settings.info.act_use'))} + ${pill(db.products_month, t('settings.info.act_new_products'))} + ${pill(db.tx_year, t('settings.info.act_tx_year'))} +
`; } // ── System card ────────────────────────────────────────────────────── if (sysEl) { - const logMb = ((d.log_bytes || 0) / 1048576).toFixed(2); - const dbMb = ((d.db_bytes || 0) / 1048576).toFixed(2); + const db = d.db || {}; + const nowTs = Math.floor(Date.now()/1000); + const bringDays = d.bring_expires_ts ? Math.round((d.bring_expires_ts - nowTs)/86400) : null; + const bringColor = bringDays !== null && bringDays <= 3 ? '#dc2626' : ''; + const bringLabel = bringDays === null ? '—' + : bringDays <= 0 ? t('settings.info.bring_expired') + : t('settings.info.bring_days').replace('{n}', bringDays); + + const lvlColors = {DEBUG:'#1e40af//#dbeafe', INFO:'#15803d//#dcfce7', WARN:'#854d0e//#fef9c3', ERROR:'#991b1b//#fee2e2'}; + const [lvlFg, lvlBg] = (lvlColors[d.log_level] || '#64748b//#f1f5f9').split('//'); + sysEl.innerHTML = ` - - - - +
+ ${pill(fmtBytes(db.bytes), t('settings.info.db_size'))} + ${pill(fmtBytes(d.log_bytes), t('settings.info.log_size'))} + ${pill(`${d.log_level||'INFO'}`, t('settings.info.log_level'))} +
+
${t('settings.info.db_size')}${dbMb} MB
+ + + - - - + + + - - - + + + -
${t('settings.info.price_cache')}${(d.caches?.price||0)} ${t('settings.info.cache_entries')}
${t('settings.info.log_size')}${logMb} MB (${d.log_files||0} files)
${t('settings.info.last_backup')}${d.last_backup_ts ? fmtDate(d.last_backup_ts)+' · '+fmtBytes(d.last_backup_bytes) : '—'}
${t('settings.info.log_level')} - ${d.log_level||'INFO'} -
Bring!${bringLabel}
- `; + `; } } catch(e) { - if (aiEl) aiEl.innerHTML = `

${t('error.generic')}

`; - if (sysEl) sysEl.innerHTML = `

${t('error.generic')}

`; + ['info-ai-content','info-inv-content','info-act-content','info-system-content'].forEach(id => { + const el = document.getElementById(id); + if (el) el.innerHTML = `

${t('error.generic')}

`; + }); } } diff --git a/index.html b/index.html index f00b1aa..fc5cecd 100644 --- a/index.html +++ b/index.html @@ -1342,16 +1342,30 @@

Gemini AI — Token Usage

-

Monthly consumption and estimated cost for the current API key.

+

Utilizzo AI, inventario e sistema

-

Loading…

+

Caricamento…

+
+
+ +
+

Inventario

+
+

Caricamento…

+
+
+ +
+

Attività del mese

+
+

Caricamento…

-

System

+

Sistema

-

Loading…

+

Caricamento…

diff --git a/translations/de.json b/translations/de.json index 0dd95c7..2441e8b 100644 --- a/translations/de.json +++ b/translations/de.json @@ -771,14 +771,37 @@ "est_cost": "Gesch. Kosten", "input_tok": "Eingabe-Token", "output_tok": "Ausgabe-Token", - "ai_calls": "KI-Aufrufe", + "ai_calls": "Aufrufe", "by_action": "Aufschlüsselung nach Funktion", "by_model": "Aufschlüsselung nach Modell", - "pricing_note": "Referenzpreise: gemini-2.5-flash $0.15/1M Input, $0.60/1M Output.", + "pricing_note": "Gemini Referenzpreise: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.", "system_title": "System", - "db_size": "Datenbankgröße", - "log_size": "Log-Größe", - "log_level": "Log-Level" + "db_size": "Datenbank", + "log_size": "Protokolle", + "log_level": "Log-Level", + "ai_overview": "KI-Nutzungsübersicht, Inventar und Systemstatus", + "tracked_section": "Erfasst (seit Monitoring-Start)", + "retro_section": "Rückwirkende Schätzung — {month}", + "retro_note": "Schätzung basiert auf KI-Cache-Dateien, die vor der Monitoring-Aktivierung generiert wurden.", + "year_section": "Jahr {year} — Gesamtschätzung", + "calls_unit": "Aufrufe", + "inv_title": "Inventar", + "inv_active": "Aktiv", + "inv_products": "Produkte gesamt", + "inv_expiring": "Ablaufend (7T)", + "inv_expired": "Abgelaufen", + "inv_finished": "Leer", + "act_title": "Monatliche Aktivität", + "act_tx_month": "Bewegungen", + "act_restock": "Einkäufe", + "act_use": "Verbrauch", + "act_new_products": "Neue Produkte", + "act_tx_year": "Jährl. Bewegungen", + "price_cache": "Preiscache", + "cache_entries": "Produkte", + "last_backup": "Letztes Backup", + "bring_days": "Token läuft in {n} Tagen ab", + "bring_expired": "Token abgelaufen" } }, "expiry": { diff --git a/translations/en.json b/translations/en.json index dfeb870..25574bd 100644 --- a/translations/en.json +++ b/translations/en.json @@ -771,14 +771,37 @@ "est_cost": "Est. cost", "input_tok": "Input tokens", "output_tok": "Output tokens", - "ai_calls": "AI calls", + "ai_calls": "Calls", "by_action": "Breakdown by function", "by_model": "Breakdown by model", - "pricing_note": "Reference pricing: gemini-2.5-flash $0.15/1M input, $0.60/1M output.", + "pricing_note": "Gemini reference pricing: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.", "system_title": "System", - "db_size": "Database size", - "log_size": "Log size", - "log_level": "Log level" + "db_size": "Database", + "log_size": "Logs", + "log_level": "Log level", + "ai_overview": "AI usage overview, inventory and system status", + "tracked_section": "Tracked (since monitoring start)", + "retro_section": "Retroactive estimate — {month}", + "retro_note": "Estimate based on AI cache files generated before monitoring was activated.", + "year_section": "Year {year} — total estimate", + "calls_unit": "calls", + "inv_title": "Inventory", + "inv_active": "Active", + "inv_products": "Total products", + "inv_expiring": "Expiring (7d)", + "inv_expired": "Expired", + "inv_finished": "Finished", + "act_title": "Monthly activity", + "act_tx_month": "Movements", + "act_restock": "Restocks", + "act_use": "Usages", + "act_new_products": "New products", + "act_tx_year": "Yearly movements", + "price_cache": "Price cache", + "cache_entries": "products", + "last_backup": "Last backup", + "bring_days": "token expires in {n} days", + "bring_expired": "token expired" } }, "expiry": { diff --git a/translations/it.json b/translations/it.json index e8e6cef..1294aba 100644 --- a/translations/it.json +++ b/translations/it.json @@ -771,14 +771,37 @@ "est_cost": "Costo stimato", "input_tok": "Token input", "output_tok": "Token output", - "ai_calls": "Chiamate AI", + "ai_calls": "Chiamate", "by_action": "Dettaglio per funzione", "by_model": "Dettaglio per modello", - "pricing_note": "Prezzi di riferimento: gemini-2.5-flash $0.15/1M input, $0.60/1M output.", + "pricing_note": "Prezzi di riferimento Gemini: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.", "system_title": "Sistema", - "db_size": "Dimensione database", - "log_size": "Dimensione log", - "log_level": "Livello di log" + "db_size": "Database", + "log_size": "Log", + "log_level": "Livello log", + "ai_overview": "Prospetto utilizzo AI, inventario e stato del sistema", + "tracked_section": "Tracciato (da inizio monitoraggio)", + "retro_section": "Stima retroattiva — {month}", + "retro_note": "Stima basata sulle voci presenti nei file di cache AI, generati prima dell'attivazione del monitoraggio.", + "year_section": "Anno {year} — totale stimato", + "calls_unit": "call", + "inv_title": "Inventario", + "inv_active": "Attivi", + "inv_products": "Prodotti totali", + "inv_expiring": "In scadenza (7gg)", + "inv_expired": "Scaduti", + "inv_finished": "Finiti", + "act_title": "Attività del mese", + "act_tx_month": "Movimenti", + "act_restock": "Acquisti", + "act_use": "Consumi", + "act_new_products": "Nuovi prodotti", + "act_tx_year": "Movimenti anno", + "price_cache": "Cache prezzi", + "cache_entries": "prodotti", + "last_backup": "Ultimo backup", + "bring_days": "token scade tra {n} giorni", + "bring_expired": "token scaduto" } }, "expiry": {