diff --git a/models.py b/models.py index 413b052..dc704a1 100644 --- a/models.py +++ b/models.py @@ -1073,7 +1073,24 @@ def get_user_active_shifts(user_id: int): # ─── Admin Dashboard ────────────────────────────────────────────────────────── +_admin_stats_cache: dict | None = None +_admin_stats_cached_at: datetime.datetime | None = None +_ADMIN_STATS_TTL_S = 60 # seconds + + +def invalidate_admin_stats_cache(): + global _admin_stats_cache, _admin_stats_cached_at + _admin_stats_cache = None + _admin_stats_cached_at = None + + def get_admin_dashboard_stats(): + global _admin_stats_cache, _admin_stats_cached_at + now = datetime.datetime.now() + if (_admin_stats_cache is not None and _admin_stats_cached_at is not None + and (now - _admin_stats_cached_at).total_seconds() < _ADMIN_STATS_TTL_S): + return _admin_stats_cache + conn = None try: conn = get_connection() @@ -1123,7 +1140,7 @@ def get_admin_dashboard_stats(): ) ai_analyses_30d = cur.fetchone()["n"] cur.close() - return { + result = { "user_stats": user_stats, "total_sites": total_sites, "total_users": total_users, @@ -1132,6 +1149,9 @@ def get_admin_dashboard_stats(): "bids_due_soon": bids_due_soon, "ai_analyses_30d": ai_analyses_30d, } + _admin_stats_cache = result + _admin_stats_cached_at = now + return result finally: if conn: conn.close() diff --git a/routes/ai_summary.py b/routes/ai_summary.py index e2326ab..2051517 100644 --- a/routes/ai_summary.py +++ b/routes/ai_summary.py @@ -108,7 +108,7 @@ def analyze(): f"AI analysis on {len(file_names)} file(s). Verdict: {result.get('verdict')}.") return jsonify({"analysis_id": analysis_id, "model": model, - "file_count": len(file_names), **result}) + "file_count": len(file_names), "file_errors": errors, **result}) # Magic-byte signatures for each supported binary format. diff --git a/static/js/app.js b/static/js/app.js index 7ec0aa4..515a039 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -75,7 +75,7 @@ document.addEventListener('DOMContentLoaded', () => { alert.style.transition = 'opacity 0.5s'; alert.style.opacity = '0'; setTimeout(() => alert.remove(), 500); - }, 5000); + }, 7000); }); }); diff --git a/templates/ai_summary.html b/templates/ai_summary.html index cf1ad5d..2073920 100644 --- a/templates/ai_summary.html +++ b/templates/ai_summary.html @@ -358,7 +358,13 @@ async function runAnalysis() { var btn = document.getElementById('btn-analyze'); btn.disabled = true; - btn.textContent = '⏳ Analysing…'; + + var _elapsed = 0; + var _elapsedTimer = setInterval(function() { + _elapsed++; + btn.textContent = '⏳ Analysing… ' + _elapsed + 's'; + }, 1000); + btn.textContent = '⏳ Analysing… 0s'; document.getElementById('ai-output').innerHTML = '
Running AI analysis… this may take a moment.