Kaynağa Gözat

Fix sentiment section: timestamp parsing, loading state, toast visibility, hours dropdown

- Fix get_sentiment_series _parse_ts in sqlite_store.py and dashboard_store.py:
  add email.utils.parsedate_to_datetime fallback for HTTP-date timestamps.
  Previously fromisoformat() silently dropped ~95% of clusters whose timestamps
  were stored as "Sat, 30 May 2026 02:00:12 +0000" (HTTP-date format).
- Add loading state to reloadSentiment() — shows placeholder while fetching,
  restores canvas before rendering chart.
- Seed sentiment-hours dropdown ascending (6h/24h/3d/week) to match clusters section,
  default to "Last week" (168h).
- Add opacity:0 to .toast CSS so the toast div is hidden until showToast() fires.
Lukas Goldschmidt 1 hafta önce
ebeveyn
işleme
20d0c84d77

+ 9 - 2
dashboard/dashboard.js

@@ -263,15 +263,22 @@ async function reloadSentiment() {
   var topic = $('sentiment-topic').value;
   var hours = $('sentiment-hours').value;
   var bucket = $('sentiment-bucket').value;
+  // Use a wrapper div so we don't depend on the canvas element surviving innerHTML replacement
+  var wrap = $('chart-sentiment') ? $('chart-sentiment').parentElement : null;
+  var statsEl = $('sentiment-stats');
+  if (statsEl) statsEl.innerHTML = '';
+  if (wrap) wrap.innerHTML = '<div class="loading">Loading sentiment data…</div>';
   try {
     var res = await fetch(API + '/sentiment-series?topic=' + encodeURIComponent(topic) + '&hours=' + hours + '&bucket_hours=' + bucket);
     var d = await res.json();
+    if (wrap && !$('chart-sentiment')) {
+      wrap.innerHTML = '<canvas id="chart-sentiment"></canvas>';
+    }
     renderSentimentChart(d.series || [], 'chart-sentiment', true);
     renderSentimentStats(d.series || []);
   } catch(e) {
     console.error('Sentiment error:', e);
-    var el = $('chart-sentiment');
-    if (el) el.parentElement.innerHTML = '<div class="loading">Error: ' + esc(e.message) + '</div>';
+    if (wrap) wrap.innerHTML = '<div class="loading">Error: ' + esc(e.message) + '</div>';
   }
 }
 

+ 4 - 4
dashboard/index.html

@@ -103,10 +103,10 @@
           <option value="ai">AI</option>
         </select>
         <select id="sentiment-hours" onchange="reloadSentiment()">
-          <option value="6">6h</option>
-          <option value="144" selected>144h</option>
-          <option value="72">72h</option>
-          <option value="168">7d</option>
+          <option value="6">Last 6h</option>
+          <option value="24">Last 24h</option>
+          <option value="72">Last 3 days</option>
+          <option value="168" selected>Last week</option>
         </select>
         <select id="sentiment-bucket" onchange="reloadSentiment()">
           <option value="1">1h buckets</option>

+ 1 - 1
dashboard/style.css

@@ -216,7 +216,7 @@ tr:hover td { background: rgba(91,138,245,.05); }
   background: var(--surface); border: 1px solid var(--border); color: var(--text);
   padding: .6rem 1.2rem; border-radius: 8px; font-size: .82rem;
   box-shadow: var(--shadow); z-index: 300; transition: opacity .3s;
-  pointer-events: none;
+  pointer-events: none; opacity: 0;
 }
 .toast-error { border-color: var(--red); color: var(--red); }
 

+ 10 - 5
news_mcp/dashboard/dashboard_store.py

@@ -3,6 +3,7 @@ from __future__ import annotations
 import json
 from datetime import datetime, timedelta, timezone
 from typing import Any
+from email.utils import parsedate_to_datetime
 
 from news_mcp.config import (
     NEWS_PRUNE_INTERVAL_HOURS,
@@ -174,13 +175,17 @@ class DashboardStore:
         def _parse_ts(ts: Any) -> datetime | None:
             if not ts:
                 return None
+            s = str(ts)
             try:
-                dt = datetime.fromisoformat(str(ts).replace("Z", "+00:00"))
-                if dt.tzinfo is None:
-                    dt = dt.replace(tzinfo=timezone.utc)
-                return dt.astimezone(timezone.utc)
+                dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
             except Exception:
-                return None
+                try:
+                    dt = parsedate_to_datetime(s)
+                except Exception:
+                    return None
+            if dt.tzinfo is None:
+                dt = dt.replace(tzinfo=timezone.utc)
+            return dt.astimezone(timezone.utc)
 
         step_hours = max(1, int(bucket_hours))
         buckets: dict[datetime, list[float]] = {}

+ 9 - 5
news_mcp/storage/sqlite_store.py

@@ -692,13 +692,17 @@ class SQLiteClusterStore:
         def _parse_ts(ts: Any) -> datetime | None:
             if not ts:
                 return None
+            s = str(ts)
             try:
-                dt = datetime.fromisoformat(str(ts).replace("Z", "+00:00"))
-                if dt.tzinfo is None:
-                    dt = dt.replace(tzinfo=timezone.utc)
-                return dt.astimezone(timezone.utc)
+                dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
             except Exception:
-                return None
+                try:
+                    dt = parsedate_to_datetime(s)
+                except Exception:
+                    return None
+            if dt.tzinfo is None:
+                dt = dt.replace(tzinfo=timezone.utc)
+            return dt.astimezone(timezone.utc)
 
         buckets: dict[datetime, list[float]] = {}
         for (payload_text,) in rows: