From 40882b62eacbb5ee41b8801d7ddc6b0d4b8054d9 Mon Sep 17 00:00:00 2001 From: Kyrykbaev-I Date: Mon, 1 Jun 2026 10:43:20 +0500 Subject: [PATCH] feat: shared AI cache via git (ai-cache.json) - AI analysis loaded from git on startup (same for all users) - Saving new response commits ai-cache.json via Gitea API - Requires GITEA_TOKEN in config.js to update cache - Update UI: auto-expanded result, collapsible refresh section Co-Authored-By: Claude Sonnet 4.6 --- ai-cache.json | 5 ++ dashboard.html | 126 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 ai-cache.json diff --git a/ai-cache.json b/ai-cache.json new file mode 100644 index 0000000..c8c6166 --- /dev/null +++ b/ai-cache.json @@ -0,0 +1,5 @@ +{ + "generated_at": null, + "period": null, + "response": null +} diff --git a/dashboard.html b/dashboard.html index a4de98f..14fb0bc 100644 --- a/dashboard.html +++ b/dashboard.html @@ -159,6 +159,15 @@ body { font-family: var(--font-base); background: var(--color-bg); color: var(-- .ai-divider { height: 1px; background: var(--color-border); margin: 12px 0; } .ai-hint { font-size: 11px; color: var(--color-text-secondary); text-align: center; line-height: 1.5; } .ai-key-note { font-size: 11px; color: var(--color-green); text-align: center; margin-bottom: 8px; font-weight: 500; } +.ai-cache-meta { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; } +.ai-update-details { margin-top: 4px; } +.ai-update-details[open] .ai-update-summary { color: var(--color-brand); } +.ai-update-summary { + font-size: 13px; font-weight: 600; color: var(--color-text-secondary); + cursor: pointer; list-style: none; padding: 6px 0; user-select: none; +} +.ai-update-summary::-webkit-details-marker { display: none; } +.ai-update-summary:hover { color: var(--color-brand); } /* ── RESPONSIVE ── */ @media (max-width: 1200px) { .summary-bar { grid-template-columns: repeat(3,1fr); } } @@ -271,16 +280,30 @@ body { font-family: var(--font-base); background: var(--color-bg); color: var(--
🤖 AI-анализ KPI
-
- - - + + + + - - + +
-

DeepSeek API · Ключ не сохраняется за пределами сессии браузера

+
+ 🔄 Обновить анализ +
+ + + +
+

Новый ответ сохранится в репо и сразу станет доступен всем пользователям. Обновляйте раз в месяц при новых данных.

+
@@ -444,7 +467,12 @@ const EMBEDDED_CSV = `"report_period_id";"entry_date";"abons";"registered_total" // ───────────────────────────────────────────────────────────────────────────── const MONTH_NAMES = ['','Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']; -const CSV_FILENAME = 'drb_iliyas_kpi_2026.csv'; +const CSV_FILENAME = 'drb_iliyas_kpi_2026.csv'; +const GITEA_API = 'https://git.telecom.quest/api/v1'; +const GITEA_OWNER = 'Kyrykbaev-I'; +const GITEA_REPO = 'kpi-dashboard'; +const AI_CACHE_FILE = 'ai-cache.json'; +const AI_CACHE_RAW = `https://git.telecom.quest/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/main/${AI_CACHE_FILE}`; const AppState = { rawRows: [], snapshots: [], dailySeries: {}, @@ -696,7 +724,7 @@ function showDashboard() { document.getElementById('dashboard').style.display = 'block'; renderHeader(); renderSummaryBar(); renderToolbar(); destroyAllCharts(); renderAllCharts(); - restoreAiCache(); initAiKey(); + loadAiCache(); initAiKey(); } function renderHeader() { @@ -1120,6 +1148,7 @@ async function runAiAnalysis() { AppState.lastAiResponse=text; sessionStorage.setItem('lastAiResponse',text); showAiResult(text); + await saveAiCache(text); // save to git repo for all users (requires GITEA_TOKEN in config.js) } catch(e) { clearTimeout(t); showAiError(e.name==='AbortError'?'Превышено время ожидания. Попробуйте снова.':'Ошибка: '+e.message); @@ -1131,6 +1160,85 @@ function showAiResult(text){document.getElementById('ai-text').textContent=text; function showAiError(msg){const el=document.getElementById('ai-error');if(msg){el.textContent=msg;el.classList.remove('hidden');}else el.classList.add('hidden');} function restoreAiCache(){const c=sessionStorage.getItem('lastAiResponse');if(c)showAiResult(c);} +// ═══════════════════ AI CACHE (git-based, shared for all users) ═══════════════════ + +/** Load AI response from ai-cache.json stored in the git repo */ +async function loadAiCache() { + const dateEl = document.getElementById('ai-cache-date'); + try { + const resp = await fetch(AI_CACHE_RAW, {cache:'no-cache'}); + if (!resp.ok) { if(dateEl) dateEl.textContent = 'Кэш не найден'; return; } + const data = await resp.json(); + if (!data.response) return; + AppState.lastAiResponse = data.response; + showAiResult(data.response); + if (dateEl && data.generated_at) { + const d = new Date(data.generated_at); + dateEl.textContent = 'Обновлено: ' + d.toLocaleDateString('ru-RU',{day:'numeric',month:'long',year:'numeric'}); + } + } catch(e) { + if(dateEl) dateEl.textContent = 'Кэш недоступен'; + console.info('AI cache not loaded:', e.message); + } +} + +/** Save AI response to ai-cache.json in git via Gitea API. + * Requires window.GITEA_TOKEN (from config.js). */ +async function saveAiCache(text) { + const token = window.GITEA_TOKEN; + if (!token) return; // no write access — cache stays local only + + const lastSnap = AppState.snapshots[AppState.snapshots.length-1]; + const payload = { + generated_at: new Date().toISOString(), + period: periodLabel(lastSnap.report_period_id), + response: text, + }; + + // base64-encode the JSON (Gitea API requires base64 content) + const content = btoa(unescape(encodeURIComponent(JSON.stringify(payload, null, 2)))); + + // Fetch current file SHA (needed for update; absent for first create) + let sha = null; + try { + const fileResp = await fetch( + `${GITEA_API}/repos/${GITEA_OWNER}/${GITEA_REPO}/contents/${AI_CACHE_FILE}`, + {headers:{'Authorization':`token ${token}`}} + ); + if (fileResp.ok) { sha = (await fileResp.json()).sha; } + } catch {} + + const body = JSON.stringify({ + message: `Update AI analysis cache — ${payload.period}`, + content, + ...(sha ? {sha} : {}), + }); + + try { + const saveResp = await fetch( + `${GITEA_API}/repos/${GITEA_OWNER}/${GITEA_REPO}/contents/${AI_CACHE_FILE}`, + { + method: 'PUT', + headers: { + 'Authorization': `token ${token}`, + 'Content-Type': 'application/json', + }, + body, + } + ); + if (saveResp.ok) { + const dateEl = document.getElementById('ai-cache-date'); + if (dateEl) dateEl.textContent = 'Обновлено: ' + + new Date().toLocaleDateString('ru-RU',{day:'numeric',month:'long',year:'numeric'}); + console.info('AI cache saved to git ✓'); + } else { + console.warn('AI cache save failed:', saveResp.status); + } + } catch(e) { + console.warn('AI cache save error:', e.message); + } +} + // ═══════════════════ EVENTS ═══════════════════ document.getElementById('file-input').addEventListener('change',function(e){ const file=e.target.files[0]; if(!file)return;