384 lines
20 KiB
HTML
384 lines
20 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}AlphaX Agent | Crypto — 舆情雷达{% endblock %}
|
||
{% block nav_links %}
|
||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||
<a class="sidebar-link active" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||
<a class="sidebar-link" href="/onchain"><svg class="link-icon"><use href="#svg-onchain"/></svg>链上异动</a>
|
||
<a class="sidebar-link" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
||
<a class="sidebar-link" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>推荐</a>
|
||
<div class="sidebar-section-label admin-link" style="display:none">研发</div>
|
||
<a class="sidebar-link admin-link" href="/pipeline" style="display:none"><svg class="link-icon"><use href="#svg-pipeline"/></svg>链路日志</a>
|
||
<a class="sidebar-link admin-link" href="/cron" style="display:none"><svg class="link-icon"><use href="#svg-cron"/></svg>调度中心</a>
|
||
<a class="sidebar-link admin-link" href="/llm-insights" style="display:none"><svg class="link-icon"><use href="#svg-ai"/></svg>AI 记录</a>
|
||
<a class="sidebar-link admin-link" href="/strategy" style="display:none"><svg class="link-icon"><use href="#svg-target"/></svg>策略</a>
|
||
<a class="sidebar-link admin-link" href="/iteration" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>迭代</a>
|
||
<a class="sidebar-link admin-link" href="/admin.html" style="display:none"><svg class="link-icon"><use href="#svg-admin"/></svg>管理</a>
|
||
{% endblock %}
|
||
{% block extra_head_css %}
|
||
<style>
|
||
|
||
/* SHELL */
|
||
.shell { position: relative; z-index: 1; width: min(100% - 40px, 960px); margin: 0 auto; padding: 24px 0; }
|
||
|
||
/* Page title */
|
||
.page-title { font-size: 24px; font-weight: 800; color: var(--ink); margin-bottom: 4px; }
|
||
.page-sub { font-size: 13px; color: var(--stone); margin-bottom: 18px; }
|
||
|
||
/* === SECTION: DASHBOARD === */
|
||
.market-context {
|
||
display: grid; grid-template-columns: minmax(210px, 260px) 1fr;
|
||
gap: 10px; margin-bottom: 18px;
|
||
}
|
||
@media(max-width:720px) { .market-context { grid-template-columns: 1fr; } }
|
||
|
||
/* Fear & Greed */
|
||
.fg-card {
|
||
background: var(--canvas); border: 1px solid var(--hairline-soft);
|
||
border-radius: var(--radius-lg); padding: 10px 12px;
|
||
display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 8px 10px;
|
||
}
|
||
.fg-card .fg-label { grid-column: 1 / -1; font-size: 11px; color: var(--stone); font-weight: 800; }
|
||
.fg-card .fg-value { font-size: 28px; font-weight: 900; line-height: 1; transition: color .3s; }
|
||
.fg-card .fg-class { justify-self: start; font-size: 12px; font-weight: 800; padding: 3px 9px; border-radius: var(--radius-full); }
|
||
.fg-gauge { grid-column: 1 / -1; width: 100%; height: 5px; border-radius: 999px; background: linear-gradient(to right, #e53e3e, #f59e0b, #84cc16); position: relative; }
|
||
.fg-gauge::after {
|
||
content: ""; position: absolute; top: -3px;
|
||
width: 11px; height: 11px; border-radius: 50%; background: var(--canvas);
|
||
border: 2px solid var(--ink); transition: left .5s;
|
||
left: calc(var(--pos, 0%) - 5px);
|
||
}
|
||
|
||
/* Trending card */
|
||
.trend-card {
|
||
background: var(--canvas); border: 1px solid var(--hairline-soft);
|
||
border-radius: var(--radius-lg); padding: 10px 12px; min-width: 0;
|
||
}
|
||
.trend-card .section-label { font-size: 11px; color: var(--stone); font-weight: 800; margin-bottom: 8px; }
|
||
.trend-list { display: flex; flex-wrap: wrap; gap: 6px; min-height: 29px; align-items: center; }
|
||
.trend-pill { display: inline-flex; align-items: center; gap: 6px; max-width: 160px; padding: 4px 8px; border: 1px solid var(--hairline-soft); border-radius: 999px; background: var(--surface); color: var(--slate); font-size: 12px; font-weight: 800; }
|
||
.trend-pill img { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; }
|
||
.trend-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
|
||
.trend-symbol { font-size: 11px; color: var(--stone); margin-left: 4px; }
|
||
.trend-rank { font-size: 10px; color: var(--muted); }
|
||
|
||
/* === SECTION: NEWS FEED === */
|
||
.feed-header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||
.feed-header h2 { font-size: 18px; font-weight: 700; }
|
||
.feed-header .feed-count { font-size: 12px; color: var(--muted); background: var(--surface); padding: 2px 10px; border-radius: var(--radius-full); }
|
||
|
||
.news-feed { display: flex; flex-direction: column; gap: 8px; }
|
||
|
||
.news-card {
|
||
background: var(--canvas); border: 1px solid var(--hairline-soft);
|
||
border-radius: var(--radius-xl); padding: 16px 18px;
|
||
transition: .15s; cursor: pointer; display: flex; gap: 14px; align-items: flex-start;
|
||
}
|
||
.news-card:hover { border-color: var(--hairline); box-shadow: 0 2px 8px rgba(5,0,56,.04); }
|
||
.news-card:active { transform: scale(.995); }
|
||
|
||
.news-source {
|
||
flex-shrink: 0; min-width: 56px; font-size: 10px; font-weight: 700;
|
||
color: var(--stone); padding: 4px 8px; background: var(--surface);
|
||
border-radius: var(--radius-md); text-align: center; white-space: nowrap;
|
||
overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.news-source.cn { color: var(--blue); background: rgba(66,98,255,.06); }
|
||
.news-body { flex: 1; min-width: 0; }
|
||
.news-title { font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 6px; }
|
||
.news-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--muted); }
|
||
.news-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--hairline); }
|
||
|
||
.ai-brief { margin-top: 8px; border: 1px solid var(--hairline-soft); border-radius: var(--radius-lg); background: var(--surface); padding: 10px 12px; }
|
||
.ai-brief .label { font-size: 10px; color: var(--stone); font-weight: 900; margin-bottom: 4px; }
|
||
.ai-brief .text { font-size: 12px; color: var(--slate); line-height: 1.55; }
|
||
.ai-brief .chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
|
||
.ai-brief .chip { display: inline-flex; padding: 3px 7px; border-radius: 999px; border: 1px solid var(--hairline-soft); background: var(--canvas); color: var(--slate); font-size: 10px; }
|
||
.analysis-card { background: var(--canvas); border: 1px solid var(--hairline-soft); border-radius: var(--radius-xl); padding: 18px; margin-bottom: 18px; }
|
||
.analysis-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:12px; }
|
||
.analysis-title { font-size: 16px; font-weight: 900; color: var(--ink); }
|
||
.analysis-meta { color: var(--stone); font-size: 11px; font-weight: 800; text-align:right; line-height:1.5; }
|
||
.mood { display:inline-flex; border-radius:999px; padding:4px 9px; font-size:11px; font-weight:900; background:var(--surface); color:var(--slate); border:1px solid var(--hairline-soft); }
|
||
.mood.risk_on { color: var(--green); background: var(--green-light); border-color: rgba(0,180,115,.18); }
|
||
.mood.risk_off { color: var(--red); background: var(--red-light); border-color: rgba(229,62,62,.18); }
|
||
.analysis-summary { color: var(--slate); font-size: 14px; line-height: 1.75; margin-bottom: 14px; }
|
||
.analysis-grid { display:grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||
.analysis-section { border:1px solid var(--hairline-soft); border-radius: var(--radius-lg); background: var(--surface); padding: 12px; min-width:0; }
|
||
.analysis-section h3 { font-size: 12px; font-weight: 900; color: var(--ink); margin-bottom: 9px; }
|
||
.analysis-item { border-top:1px solid var(--hairline-soft); padding:8px 0; color:var(--slate); font-size:12px; line-height:1.55; }
|
||
.analysis-item:first-of-type { border-top:0; padding-top:0; }
|
||
.analysis-item b { color:var(--ink); font-size:13px; }
|
||
.analysis-item .sub { display:block; margin-top:3px; color:var(--stone); }
|
||
.symbol-tags { display:flex; flex-wrap:wrap; gap:4px; margin-top:5px; }
|
||
.symbol-tag { display:inline-flex; padding:3px 7px; border-radius:999px; background:var(--canvas); border:1px solid var(--hairline-soft); color:var(--blue); font-size:10px; font-weight:900; }
|
||
@media(max-width:760px){ .analysis-grid{grid-template-columns:1fr;} .analysis-head{display:block;} .analysis-meta{text-align:left;margin-top:8px;} }
|
||
|
||
/* Empty */
|
||
.empty-state { text-align:center; padding:48px 20px; color:var(--stone); }
|
||
.empty-state p { font-size:14px; }
|
||
|
||
/* Loading */
|
||
.loading-pulse { animation: pulse 1.5s ease-in-out infinite; }
|
||
@keyframes pulse { 0%,100%{ opacity:1 } 50%{ opacity:.3 } }
|
||
.spin { animation: spin 1s linear infinite; }
|
||
@keyframes spin { to{ transform:rotate(360deg) } }
|
||
|
||
@media(max-width:640px) {
|
||
.shell { width: min(100% - 24px, 960px); }
|
||
.news-card { padding: 14px 14px; gap: 10px; }
|
||
.news-source { min-width: 48px; font-size: 9px; padding: 3px 6px; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
<div class="shell">
|
||
<h1 class="page-title">实时舆情</h1>
|
||
<p class="page-sub">AI 舆情研判 + 本轮分析来源</p>
|
||
|
||
<div id="aiAnalysis" class="analysis-card">
|
||
<div class="empty-state"><p>等待 AI 舆情分析结果...</p></div>
|
||
</div>
|
||
|
||
<!-- Dashboard -->
|
||
<div class="market-context">
|
||
<div class="fg-card" id="fgCard">
|
||
<div class="fg-label">恐惧 & 贪婪指数</div>
|
||
<div class="fg-value loading-pulse" id="fgValue">--</div>
|
||
<div class="fg-class" id="fgClass">加载中</div>
|
||
<div class="fg-gauge" id="fgGauge"></div>
|
||
</div>
|
||
|
||
<div class="trend-card">
|
||
<div class="section-label">CoinGecko 热门币种</div>
|
||
<div class="trend-list loading-pulse" id="trendList">
|
||
<span class="trend-pill">加载中...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Source Feed -->
|
||
<div class="feed-header">
|
||
<h2>本轮分析来源</h2>
|
||
<span class="feed-count" id="feedCount">--</span>
|
||
</div>
|
||
<div class="news-feed" id="newsFeed">
|
||
<div class="empty-state"><p>加载中...</p></div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
{% block extra_script %}
|
||
<script>
|
||
var API = '';
|
||
|
||
// ====== USER ======
|
||
var currentUser = null;
|
||
var $ = function(id){ return document.getElementById(id); };
|
||
|
||
async function loadUser() {
|
||
try {
|
||
var resp = await fetch(API + '/api/auth/me');
|
||
if (!resp.ok) return;
|
||
var data = await resp.json();
|
||
currentUser = data.user;
|
||
var email = currentUser.email || '--';
|
||
$('userInitial').textContent = email.charAt(0).toUpperCase();
|
||
$('userEmailShort').textContent = email.length > 14 ? email.slice(0,12) + '…' : email;
|
||
$('ddEmail').textContent = email;
|
||
} catch(e) {}
|
||
}
|
||
|
||
function toggleUserMenu() {
|
||
$('userDropdown').classList.toggle('show');
|
||
}
|
||
document.addEventListener('click', function(e) {
|
||
if (!e.target.closest('.sidebar-user') && !e.target.closest('.user-dropdown')) $('userDropdown').classList.remove('show');
|
||
});
|
||
|
||
function showChangePwd() {
|
||
$('userDropdown').classList.remove('show');
|
||
$('pwdModal').classList.add('show');
|
||
$('oldPwd').value = ''; $('newPwd').value = ''; $('cfmPwd').value = ''; $('pwdMsg').textContent = '';
|
||
}
|
||
function closePwdModal() { $('pwdModal').classList.remove('show'); }
|
||
|
||
async function changePwd() {
|
||
var old = $('oldPwd').value, nw = $('newPwd').value, cf = $('cfmPwd').value;
|
||
if (!old || !nw) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '请填写所有字段'; return; }
|
||
if (nw.length < 8) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '新密码至少 8 位'; return; }
|
||
if (nw !== cf) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '两次密码不一致'; return; }
|
||
try {
|
||
var r = await fetch(API + '/api/auth/change-password', {
|
||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({old_password: old, new_password: nw})
|
||
});
|
||
var d = await r.json();
|
||
if (!r.ok) throw new Error(d.detail || '修改失败');
|
||
$('pwdMsg').className = 'modal-msg ok'; $('pwdMsg').textContent = d.message || '修改成功';
|
||
setTimeout(closePwdModal, 1200);
|
||
} catch(e) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = e.message; }
|
||
}
|
||
|
||
async function doLogout() {
|
||
await fetch(API + '/api/auth/logout', { method: 'POST' });
|
||
window.location.href = '/auth';
|
||
}
|
||
|
||
// ====== FEED ======
|
||
|
||
function ageStr(h) {
|
||
if (h == null) return '';
|
||
if (h < 1) return Math.round(h * 60) + '分钟前';
|
||
if (h < 24) return Math.floor(h) + '小时前';
|
||
return Math.floor(h / 24) + '天前';
|
||
}
|
||
|
||
function esc(v){ return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];}); }
|
||
|
||
function fgColor(v) {
|
||
if (v <= 25) return 'var(--red)';
|
||
if (v <= 45) return 'var(--orange)';
|
||
if (v <= 55) return 'var(--yellow)';
|
||
if (v <= 75) return 'var(--green)';
|
||
return 'var(--green)';
|
||
}
|
||
|
||
function fmtTime(t) {
|
||
if (!t) return '--';
|
||
var d = new Date(t);
|
||
if (isNaN(d.getTime())) return t;
|
||
return (d.getMonth()+1)+'/'+d.getDate()+' '+String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0');
|
||
}
|
||
|
||
function renderAnalysis(resp) {
|
||
var box = document.getElementById('aiAnalysis');
|
||
var a = resp && resp.analysis ? resp.analysis : null;
|
||
if (!a) {
|
||
var msg = resp && resp.error ? ('AI 舆情分析未完成:' + resp.error) : '暂无 AI 舆情分析。调度器会定时生成,或运行 llm-insights --scope sentiment。';
|
||
box.innerHTML = '<div class="empty-state"><p>'+esc(msg)+'</p></div>';
|
||
return;
|
||
}
|
||
var mood = a.market_mood || 'neutral';
|
||
function symbolsHtml(items) {
|
||
return (items || []).slice(0, 8).map(function(s){ return '<span class="symbol-tag">'+esc(s)+'</span>'; }).join('');
|
||
}
|
||
var themes = (a.hot_themes || []).slice(0, 6).map(function(x){
|
||
return '<div class="analysis-item"><b>'+esc(x.theme || '主题')+'</b><span class="sub">'+esc(x.impact || x.reason || '--')+'</span><div class="symbol-tags">'+symbolsHtml(x.symbols || [])+'</div></div>';
|
||
}).join('') || '<div class="analysis-item">暂无明确主题</div>';
|
||
var impacts = (a.coin_impacts || []).slice(0, 8).map(function(x){
|
||
var check = x.need_technical_check ? ' · 需要技术检查' : '';
|
||
return '<div class="analysis-item"><b>'+esc(x.symbol || '--')+'</b><span class="sub">'+esc((x.direction || 'neutral') + check)+'</span><span class="sub">'+esc(x.reason || '--')+'</span></div>';
|
||
}).join('') || '<div class="analysis-item">暂无币种影响</div>';
|
||
var risks = (a.risk_events || []).slice(0, 6).map(function(x){
|
||
return '<div class="analysis-item"><b>'+esc(x.risk_type || x.title || '风险事件')+'</b><span class="sub">'+esc(x.title || x.reason || '--')+'</span><div class="symbol-tags">'+symbolsHtml(x.symbols || [])+'</div></div>';
|
||
}).join('') || '<div class="analysis-item">暂无集中风险</div>';
|
||
var watch = (a.watchlist || []).slice(0, 8).map(function(x){
|
||
return '<div class="analysis-item"><b>'+esc(x.symbol || '--')+'</b><span class="sub">'+esc(x.why || '--')+'</span><span class="sub">触发条件:'+esc(x.trigger || '--')+'</span></div>';
|
||
}).join('') || '<div class="analysis-item">暂无重点观察</div>';
|
||
box.innerHTML =
|
||
'<div class="analysis-head"><div><div class="analysis-title">AI 舆情研判 <span class="mood '+esc(mood)+'">'+esc(mood)+'</span></div></div><div class="analysis-meta">模型 '+esc(resp.model || '--')+'<br>更新 '+fmtTime(resp.updated_at)+' · 来源 '+esc(resp.event_count || 0)+' 条</div></div>'+
|
||
'<div class="analysis-summary">'+esc(a.summary || '暂无摘要')+'</div>'+
|
||
'<div class="analysis-grid">'+
|
||
'<div class="analysis-section"><h3>主线主题</h3>'+themes+'</div>'+
|
||
'<div class="analysis-section"><h3>币种影响</h3>'+impacts+'</div>'+
|
||
'<div class="analysis-section"><h3>风险事件</h3>'+risks+'</div>'+
|
||
'<div class="analysis-section"><h3>重点观察</h3>'+watch+'</div>'+
|
||
'</div>';
|
||
}
|
||
|
||
async function loadFeed() {
|
||
try {
|
||
var resp = await fetch(API + '/api/newsfeed');
|
||
var data = await resp.json();
|
||
var analysisResp = {};
|
||
try {
|
||
var ar = await fetch(API + '/api/sentiment/analysis');
|
||
if (ar.ok) analysisResp = await ar.json();
|
||
} catch(e) {}
|
||
renderAnalysis(analysisResp);
|
||
|
||
// Fear & Greed
|
||
var fg = data.fear_greed;
|
||
if (fg) {
|
||
var v = fg.value, cls = fg.classification, clr = fgColor(v);
|
||
document.getElementById('fgValue').textContent = v;
|
||
document.getElementById('fgValue').style.color = clr;
|
||
document.getElementById('fgValue').classList.remove('loading-pulse');
|
||
document.getElementById('fgClass').textContent = cls;
|
||
document.getElementById('fgClass').style.color = clr;
|
||
document.getElementById('fgClass').style.background = clr + '15';
|
||
document.getElementById('fgGauge').style.setProperty('--pos', v + '%');
|
||
}
|
||
|
||
// Trending
|
||
var trends = data.trending || [];
|
||
document.getElementById('trendList').classList.remove('loading-pulse');
|
||
if (trends.length) {
|
||
document.getElementById('trendList').innerHTML = trends.slice(0, 8).map(function(t) {
|
||
var symbol = String(t.symbol || '').toUpperCase();
|
||
var icon = t.thumb ? '<img src="' + esc(t.thumb) + '" alt="' + esc(symbol) + '" onerror="this.remove()">' : '';
|
||
return '<span class="trend-pill">' +
|
||
icon +
|
||
'<span class="trend-name">' + esc(t.name || symbol || '--') + '</span>' +
|
||
'<span class="trend-symbol">' + esc(symbol) + '</span>' +
|
||
'<span class="trend-rank">#' + esc(t.market_cap_rank || '--') + '</span>' +
|
||
'</span>';
|
||
}).join('');
|
||
} else {
|
||
document.getElementById('trendList').innerHTML = '<span style="color:var(--muted);font-size:12px">暂无数据</span>';
|
||
}
|
||
|
||
// Source feed: only show the events/news that fed the latest AI analysis.
|
||
var events = ((analysisResp && analysisResp.source_events) || []).map(function(e){
|
||
return {
|
||
title: e.title,
|
||
url: e.url,
|
||
source: e.source_label || e.source || '事件',
|
||
age_hours: null,
|
||
lang: e.related_base || '事件',
|
||
llm_insight: null,
|
||
relation_tag: e.related_symbol || e.related_base || '',
|
||
importance: e.importance
|
||
};
|
||
});
|
||
var news = events.slice(0, 50);
|
||
document.getElementById('feedCount').textContent = news.length + ' 条';
|
||
if (news.length) {
|
||
document.getElementById('newsFeed').innerHTML = news.map(function(n) {
|
||
var isCn = n.lang === 'cn';
|
||
var langLabel = n.relation_tag || (isCn ? '中文' : (n.importance ? ('重要性 ' + n.importance) : 'EN'));
|
||
var ai = n.llm_insight && n.llm_insight.content ? n.llm_insight.content : null;
|
||
var tags = ai ? (ai.key_tags || ai.theme_tags || ai.risk_types || []) : [];
|
||
var aiHtml = ai ? (
|
||
'<div class="ai-brief">' +
|
||
'<div class="label">AI 解读</div>' +
|
||
'<div class="text">' + esc(ai.summary || ai.why_now_or_not || '暂无摘要') + '</div>' +
|
||
'<div class="chips">' + (tags.slice(0,4).map(function(x){ return '<span class="chip">'+esc(x)+'</span>'; }).join('') || '') + '</div>' +
|
||
'</div>'
|
||
) : '';
|
||
return '<a class="news-card" href="' + esc(n.url || '#') + '" target="_blank" rel="noopener">' +
|
||
'<span class="news-source' + (isCn ? ' cn' : '') + '">' + esc(n.source) + '</span>' +
|
||
'<div class="news-body">' +
|
||
'<div class="news-title">' + esc(n.title) + '</div>' +
|
||
'<div class="news-meta">' +
|
||
'<span>' + esc(langLabel) + '</span>' +
|
||
'<span class="dot"></span>' +
|
||
'<span>' + ageStr(n.age_hours) + '</span>' +
|
||
'</div>' +
|
||
aiHtml +
|
||
'</div>' +
|
||
'</a>';
|
||
}).join('');
|
||
} else {
|
||
document.getElementById('newsFeed').innerHTML = '<div class="empty-state"><p>暂无新闻数据</p></div>';
|
||
}
|
||
} catch(e) {
|
||
document.getElementById('newsFeed').innerHTML = '<div class="empty-state"><p>加载失败,请稍后重试</p></div>';
|
||
}
|
||
}
|
||
|
||
loadUser();
|
||
loadFeed();
|
||
// Auto-refresh every 5 minutes
|
||
setInterval(loadFeed, 300000);
|
||
</script>
|
||
{% endblock %}
|