feat: news ticker shows flagged hourly items
This commit is contained in:
parent
610666ef6a
commit
3b4dd49253
1 changed files with 47 additions and 21 deletions
|
|
@ -539,11 +539,14 @@
|
|||
.tick-item .dom { font-family: 'Share Tech Mono', monospace; font-size: 0.64rem; color: var(--cyan); background: rgba(53,224,255,0.08); border: 1px solid rgba(53,224,255,0.22); border-radius: 3px; padding: 0.05rem 0.4rem; }
|
||||
.tick-item .tt { font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted); }
|
||||
.tick-item .sep { color: var(--line-hi); }
|
||||
button.tick-item { cursor: pointer; background: transparent; border: 0; font: inherit; color: inherit; }
|
||||
.tick-item.brief {
|
||||
cursor: pointer; background: linear-gradient(90deg, rgba(255,46,151,0.12), transparent 70%);
|
||||
border-left: 2px solid var(--magenta); font-weight: 600;
|
||||
}
|
||||
.tick-item.brief .b-tag { font-family: 'Share Tech Mono', monospace; font-size: 0.6rem; color: #1c0311; background: var(--magenta); border-radius: 2px; padding: 0.08rem 0.4rem; letter-spacing: 0.1em; }
|
||||
.tick-item .b-tag { font-family: 'Share Tech Mono', monospace; font-size: 0.6rem; color: #1c0311; background: var(--magenta); border-radius: 2px; padding: 0.08rem 0.4rem; letter-spacing: 0.1em; }
|
||||
.tick-item .b-tag.critical { background: var(--red); }
|
||||
.tick-item .b-tag.high { background: var(--amber); }
|
||||
.tick-item.brief:hover { color: var(--magenta); }
|
||||
.tick-item.standby { color: var(--muted); opacity: 0.75; }
|
||||
.tick-item.standby .price { color: var(--muted); }
|
||||
|
|
@ -827,7 +830,7 @@
|
|||
<div class="ns-kicker">Executive Summary <span class="ns-badge" id="ns-badge">WAITING</span></div>
|
||||
<div class="ns-time" id="ns-time">—</div>
|
||||
<div class="ns-body" id="ns-body">
|
||||
<div class="ns-empty">No executive summary yet — the Gemini summarizer is idle until <code>GEMINI_API_KEY</code> is set on the Pi.</div>
|
||||
<div class="ns-empty">No executive summary yet — the Nous summarizer is idle until <code>NOUS_API_KEY</code> is set on the Pi and a model is chosen in Settings.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1323,13 +1326,16 @@ async function loadNews(force) {
|
|||
if (upd) upd.textContent = 'updating…';
|
||||
let articles = [];
|
||||
let summaries = [];
|
||||
let tickerItems = [];
|
||||
try {
|
||||
const [rArticles, rSumm] = await Promise.all([
|
||||
const [rArticles, rSumm, rTick] = await Promise.all([
|
||||
fetch(`${API}/api/news?limit=100`),
|
||||
fetch(`${API}/api/news/summaries?limit=1`)
|
||||
fetch(`${API}/api/news/summaries?limit=1`),
|
||||
fetch(`${API}/api/news/ticker?limit=20`),
|
||||
]);
|
||||
if (rArticles.ok) articles = await rArticles.json();
|
||||
if (rSumm.ok) summaries = await rSumm.json();
|
||||
if (rTick.ok) tickerItems = await rTick.json();
|
||||
} catch (e) {
|
||||
const list = document.getElementById('news-list');
|
||||
const body = document.getElementById('ns-body');
|
||||
|
|
@ -1341,7 +1347,7 @@ async function loadNews(force) {
|
|||
}
|
||||
renderNewsSummary(summaries);
|
||||
renderNewsList(articles);
|
||||
renderNewsTicker(articles, summaries);
|
||||
renderNewsTicker(articles, summaries, tickerItems);
|
||||
if (upd) upd.textContent = 'updated ' + new Date().toLocaleTimeString();
|
||||
if (!newsInterval) {
|
||||
newsInterval = setInterval(() => loadNews(false), NEWS_REFRESH_MS);
|
||||
|
|
@ -1353,7 +1359,7 @@ function renderNewsSummary(summaries) {
|
|||
const badgeEl = document.getElementById('ns-badge');
|
||||
if (!bodyEl) return;
|
||||
if (!summaries || !summaries.length) {
|
||||
bodyEl.innerHTML = '<div class="ns-empty">No executive summary yet — the Gemini summarizer is idle until <code>GEMINI_API_KEY</code> is set on the Pi.</div>';
|
||||
bodyEl.innerHTML = '<div class="ns-empty">No executive summary yet — the Nous summarizer is idle until <code>NOUS_API_KEY</code> is set on the Pi and a model is chosen in Settings.</div>';
|
||||
timeEl.textContent = '—';
|
||||
badgeEl.textContent = 'WAITING';
|
||||
return;
|
||||
|
|
@ -1381,25 +1387,45 @@ function renderNewsList(articles) {
|
|||
'</a>';
|
||||
}).join('');
|
||||
}
|
||||
function renderNewsTicker(articles, summaries) {
|
||||
function renderNewsTicker(articles, summaries, tickerItems) {
|
||||
const track = document.getElementById('nt-track');
|
||||
if (!track) return;
|
||||
let items = '';
|
||||
// LLM executive summary flash first (click → News view)
|
||||
const summ = summaries && summaries.length ? summaries[0] : null;
|
||||
if (summ && summ.summary_text) {
|
||||
const brief = summ.summary_text.replace(/\s+/g, ' ').trim();
|
||||
items += `<button class="tick-item brief" onclick="showView('news')" title="Open full brief">` +
|
||||
`<span class="b-tag">LLM BRIEF</span><span>${newsEsc(brief.slice(0, 200))}${brief.length > 200 ? '…' : ''}</span>` +
|
||||
`<span class="tt">${newsTimeAgo(summ.batch_timestamp)}</span><span class="sep">▸</span></button>`;
|
||||
const ticks = Array.isArray(tickerItems) ? tickerItems : [];
|
||||
if (ticks.length) {
|
||||
ticks.forEach(t => {
|
||||
const imp = String(t.importance || '').toLowerCase();
|
||||
const tag = imp === 'critical' ? 'CRITICAL' : 'HIGH';
|
||||
const loc = (t.location_name || '').trim();
|
||||
const inner =
|
||||
`<span class="b-tag ${imp === 'critical' ? 'critical' : 'high'}">${tag}</span>` +
|
||||
`<span>${newsEsc((t.headline || '').trim())}</span>` +
|
||||
(loc ? `<span class="dom">${newsEsc(loc)}</span>` : '') +
|
||||
`<span class="tt">${newsTimeAgo(t.created_at)}</span><span class="sep">▸</span>`;
|
||||
const url = (t.url || '').trim();
|
||||
if (url) {
|
||||
items += `<a class="tick-item" href="${newsEsc(url)}" target="_blank" rel="noopener">${inner}</a>`;
|
||||
} else {
|
||||
items += `<button class="tick-item" onclick="showView('news')" title="Open news brief">${inner}</button>`;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Quiet hour: brief chip + article titles so the dock is never blank.
|
||||
const summ = summaries && summaries.length ? summaries[0] : null;
|
||||
const briefSrc = summ && (summ.summary_en || summ.summary_text);
|
||||
if (briefSrc) {
|
||||
const brief = String(briefSrc).replace(/\s+/g, ' ').trim();
|
||||
items += `<button class="tick-item brief" onclick="showView('news')" title="Open full brief">` +
|
||||
`<span class="b-tag">LLM BRIEF</span><span>${newsEsc(brief.slice(0, 120))}${brief.length > 120 ? '…' : ''}</span>` +
|
||||
`<span class="tt">${newsTimeAgo(summ.batch_timestamp)}</span><span class="sep">▸</span></button>`;
|
||||
}
|
||||
(articles || []).slice(0, 10).forEach(a => {
|
||||
items += `<a class="tick-item" href="${newsEsc(a.url || '#')}" target="_blank" rel="noopener">` +
|
||||
`<span class="dom">${newsEsc(a.domain || '?')}</span>` +
|
||||
`<span>${newsEsc((a.title || '').trim())}</span>` +
|
||||
`<span class="tt">${newsTimeAgo(a.timestamp)}</span><span class="sep">▸</span></a>`;
|
||||
});
|
||||
}
|
||||
// Top headlines
|
||||
(articles || []).slice(0, 30).forEach(a => {
|
||||
items += `<a class="tick-item" href="${newsEsc(a.url || '#')}" target="_blank" rel="noopener">` +
|
||||
`<span class="dom">${newsEsc(a.domain || '?')}</span>` +
|
||||
`<span>${newsEsc((a.title || '').trim())}</span>` +
|
||||
`<span class="tt">${newsTimeAgo(a.timestamp)}</span><span class="sep">▸</span></a>`;
|
||||
});
|
||||
if (!items) {
|
||||
items = `<span class="tick-item standby">No headlines yet — scraper idle</span>`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue