diff --git a/static/app.js b/static/app.js index 62483d8..a9f420e 100644 --- a/static/app.js +++ b/static/app.js @@ -1,32 +1,37 @@ /** * LLM 论文图书馆 — 前端 JS - * 页面加载检测 arXiv/HF 连通性 → 底部状态条 - * 点击论文:arXiv 连通? → iframe 直连 arXiv → 5s 超时 → HK 兜底 - * IDM 拦就拦,不额外对抗 + * 打开时 ping 所有论文源 → 底部状态条 → 点击论文直用缓存结果 */ const API = '/api'; let modules = {}; let moduleData = {}; +let currentPdf = null; let pdfTimeout = null; -let networkStatus = {}; +let networkStatus = {}; // { arxiv: bool, huggingface: bool, hk: bool } -const $ = (sel) => document.querySelector(sel); +const $ = (sel) => document.querySelector(sel); const $$ = (sel) => document.querySelectorAll(sel); const TAG_CLASS = { '起点':'tag-start','关键节点':'tag-milestone','前沿':'tag-frontier','前瞻':'tag-forward','支线':'tag-branch' }; -// ══════════════════ INIT ═══════════════════════════════ +// ═══════════════ INIT ═══════════════════════════════════ async function init() { + // Build network status bar buildStatusBar(); + // Load modules try { const resp = await fetch(`${API}/modules`); const mods = await resp.json(); + modules = {}; for (const m of mods) modules[m.id] = m; renderCards(mods); attachGlowTracking(); } catch (e) { console.error(e); } + + // Ping sources checkSources(); + document.addEventListener('keydown', e => { if (e.key === 'Escape') { if ($('#pdfOverlay')?.classList.contains('open')) closePdf(); @@ -35,31 +40,37 @@ async function init() { }); } -// ══════════════════ STATUS BAR ════════════════════════ +// ═══════════════ STATUS BAR ════════════════════════════ function buildStatusBar() { const bar = document.createElement('div'); - bar.id = 'statusBar'; bar.className = 'status-bar'; - bar.innerHTML = `连通性检测 + bar.id = 'statusBar'; + bar.className = 'status-bar'; + bar.innerHTML = ` + 连通性检测 arXiv — - HuggingFace —`; + HuggingFace — + `; document.body.appendChild(bar); } function setStatus(id, ok, ms, aborted) { networkStatus[id.replace('status-','')] = ok; - const el = document.getElementById(id); if (!el) return; - el.querySelector('.status-dot').className = 'status-dot ' + (ok ? 'status-ok' : 'status-fail'); - const msEl = document.getElementById('ms-'+id.replace('status-','')); + const el = document.getElementById(id); + if (!el) return; + const dot = el.querySelector('.status-dot'); + dot.className = 'status-dot ' + (ok ? 'status-ok' : 'status-fail'); + const msEl = document.getElementById('ms-' + id.replace('status-','')); if (msEl) { - if (aborted) msEl.textContent = '超时'; - else if (ok) msEl.textContent = ms+'ms'; - else msEl.textContent = '—'; + if (aborted) msEl.textContent = '超时 (4s)'; + else if (ok) msEl.textContent = ms + 'ms'; + else msEl.textContent = '—'; } } async function checkSource(name, url, statusId) { const start = performance.now(); let aborted = false; + // Add cache-buster and force no-store to avoid browser caching const probeUrl = url + '?_=' + Date.now(); try { const ctrl = new AbortController(); @@ -73,26 +84,26 @@ async function checkSource(name, url, statusId) { } } -function checkSources() { +async function checkSources() { checkSource('arxiv', 'https://arxiv.org/favicon.ico', 'status-arxiv'); - checkSource('hf', 'https://huggingface.co/favicon.ico', 'status-hf'); + checkSource('hf', 'https://huggingface.co/favicon.ico', 'status-hf'); } -// ══════════════════ GLOW ══════════════════════════════ +// ═══════════════ GLOW ══════════════════════════════════ function attachGlowTracking() { $$('.card').forEach(card => { card.addEventListener('mousemove', e => { - const r = card.getBoundingClientRect(); - card.style.setProperty('--mx', (e.clientX-r.left)/r.width*100+'%'); - card.style.setProperty('--my', (e.clientY-r.top)/r.height*100+'%'); + const rect = card.getBoundingClientRect(); + card.style.setProperty('--mx', ((e.clientX - rect.left) / rect.width * 100) + '%'); + card.style.setProperty('--my', ((e.clientY - rect.top) / rect.height * 100) + '%'); }); }); } -// ══════════════════ CARDS → MODAL → PAPERS ═══════════ +// ═══════════════ CARDS -> MODAL -> PAPERS ═══════════════ function renderCards(mods) { - $('#moduleGrid').innerHTML = mods.map(m => - `
暂无论文数据
'; + let h = ''; + function s(l, ps, c) { return ps.length ? `暂无论文数据
'; } function renderPaper(p) { const pdfUrl = getPdfLink(p); - const tags = (p.tags||[]).map(t=>`${t}`).join(' '); + const id = 'p'+Math.random().toString(36).slice(2,8); + const tags = (p.tags||[]).map(t => `${t}`).join(' '); const links = []; if (pdfUrl) links.push(``); else if (p.arxiv) links.push(`📋 arXiv`); - return `