/** * LLM 论文图书馆 — 前端 JS * 页面加载检测 arXiv/HF 连通性 → 底部状态条 * 点击论文:arXiv 连通? → iframe 直连 arXiv → 5s 超时 → HK 兜底 * IDM 拦就拦,不额外对抗 */ const API = '/api'; let modules = {}; let moduleData = {}; let pdfTimeout = null; let networkStatus = {}; const $ = (sel) => document.querySelector(sel); const $$ = (sel) => document.querySelectorAll(sel); const TAG_CLASS = { '起点':'tag-start','关键节点':'tag-milestone','前沿':'tag-frontier','前瞻':'tag-forward','支线':'tag-branch' }; // ══════════════════ INIT ═══════════════════════════════ async function init() { buildStatusBar(); try { const resp = await fetch(`${API}/modules`); const mods = await resp.json(); for (const m of mods) modules[m.id] = m; renderCards(mods); attachGlowTracking(); } catch (e) { console.error(e); } checkSources(); document.addEventListener('keydown', e => { if (e.key === 'Escape') { if ($('#pdfOverlay')?.classList.contains('open')) closePdf(); else if ($('#overlay').classList.contains('open')) closeModal(); } }); } // ══════════════════ STATUS BAR ════════════════════════ function buildStatusBar() { const bar = document.createElement('div'); bar.id = 'statusBar'; bar.className = 'status-bar'; bar.innerHTML = `连通性检测 arXiv 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-','')); if (msEl) { if (aborted) msEl.textContent = '超时'; else if (ok) msEl.textContent = ms+'ms'; else msEl.textContent = '—'; } } async function checkSource(name, url, statusId) { const start = performance.now(); let aborted = false; const probeUrl = url + '?_=' + Date.now(); try { const ctrl = new AbortController(); setTimeout(() => { aborted = true; ctrl.abort(); }, 4000); await fetch(probeUrl, { mode: 'no-cors', signal: ctrl.signal, cache: 'no-store' }); const ms = Math.round(performance.now() - start); setStatus(statusId, true, ms, false); } catch { const ms = Math.round(performance.now() - start); setStatus(statusId, false, ms, aborted); } } function checkSources() { checkSource('arxiv', 'https://arxiv.org/favicon.ico', 'status-arxiv'); checkSource('hf', 'https://huggingface.co/favicon.ico', 'status-hf'); } // ══════════════════ 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+'%'); }); }); } // ══════════════════ CARDS → MODAL → PAPERS ═══════════ function renderCards(mods) { $('#moduleGrid').innerHTML = mods.map(m => `
${m.icon} ${m.name}
${m.desc}
${m.area_count} 子领域 · ${m.paper_count} 篇
`).join(''); } async function openModule(modId) { let data = moduleData[modId]; if (!data) { try { data = await (await fetch(`${API}/modules/${modId}`)).json(); moduleData[modId]=data; } catch { return; } } const mod = modules[modId]; $('#modalTitle').innerHTML = `${mod?.icon||''} ${data.name}`; $('#modalSubtitle').textContent = data.desc||''; const areas = data.areas||[]; $('#modalTabs').innerHTML = areas.map((a,i)=>``).join(''); if (areas.length) { $('#modalTabs').dataset.areaIdx='0'; renderPapers(areas[0]); } $('#overlay').classList.add('open'); } function switchArea(idx) { const data = Object.values(moduleData).find(d => d.areas && d.areas[idx]); if (!data) return; $$('#modalTabs .tab').forEach((t,i)=>t.classList.toggle('active',i===idx)); renderPapers(data.areas[idx]); } function closeModal() { $('#overlay').classList.remove('open'); } function renderPapers(area) { const s = (l, ps, c) => ps.length ? `
${l}
`+ps.map(renderPaper).join('') : ''; $('#modalContent').innerHTML = s('📌 主线论文', area.mainline||[],'mainline') + s('🌿 支线论文', area.branches||[],'branch') + s('🔮 前瞻探索', area.forward||[],'forward') || '

暂无论文数据

'; } function renderPaper(p) { const pdfUrl = getPdfLink(p); const tags = (p.tags||[]).map(t=>`${t}`).join(' '); const links = []; if (pdfUrl) links.push(``); else if (p.arxiv) links.push(`📋 arXiv`); return `
${p.year||'—'}
${p.title}
${p.authors||''}${p.venue?`${p.venue}`:''}${tags}
`; } function getPdfLink(p) { // 有 pdf 字段 → 返回外部源 URL(arxiv 直连 / HF 直连) if (p.pdf) return p.pdf; // 只有 arxiv id → arXiv 直连 if (p.arxiv) return `https://arxiv.org/pdf/${p.arxiv}.pdf`; return null; } function openPdfBtn(btn) { openPdf(btn.dataset.pdf, btn.dataset.title); } // ══════════════════ PDF VIEWER ════════════════════════ function getLocalUrl(extUrl) { // arXiv const am = extUrl.match(/arxiv\.org\/pdf\/(\d+\.\d+)/); if (am) return `/papers/arxiv/${am[1]}.pdf`; // HuggingFace if (extUrl.includes('huggingface.co')) { const name = decodeURIComponent(extUrl).split('/').pop().replace('.pdf',''); return `/papers/hf/${name}.pdf`; } return null; } function openPdf(url, title) { const decodedUrl = decodeURIComponent(url); const decodedTitle = decodeURIComponent(title); // 构建 overlay if (!$('#pdfOverlay')) { const div = document.createElement('div'); div.id='pdfOverlay'; div.className='pdf-overlay'; div.innerHTML = `
PDF
`; document.body.appendChild(div); div.addEventListener('click', e => { if (e.target===div) closePdf(); }); } if (pdfTimeout) { clearTimeout(pdfTimeout); pdfTimeout=null; } $('#pdfTitle').textContent = decodedTitle; $('#pdfStatus').textContent = ''; const frame = $('#pdfFrame'); let loaded = false; frame.src = 'about:blank'; frame.onload = ()=>{ loaded=true; if(pdfTimeout){clearTimeout(pdfTimeout);pdfTimeout=null;} $('#pdfStatus').textContent=''; }; const hkLocalUrl = getLocalUrl(decodedUrl); const isArxiv = decodedUrl.includes('arxiv.org'); const isHF = decodedUrl.includes('huggingface.co'); const isRemote = isArxiv || isHF; const sourceOk = isArxiv ? networkStatus['arxiv'] !== false : isHF ? networkStatus['hf'] !== false : false; // 弹框先出 $('#pdfOverlay').classList.add('open'); if (isRemote && sourceOk) { // 远程源 iframe 直连 loaded = false; $('#pdfStatus').textContent = isArxiv ? '🌐 arXiv 加载中...' : '🤗 HuggingFace 加载中...'; frame.src = decodedUrl; pdfTimeout = setTimeout(() => { if (loaded) return; if (!hkLocalUrl) { $('#pdfStatus').textContent='⚠️ 超时'; return; } $('#pdfStatus').textContent = '⏳ 超时,走 HK 服务器...'; frame.src = hkLocalUrl; }, 5000); } else { // 直接走 HK $('#pdfStatus').textContent = hkLocalUrl ? '📂 HK 服务器加载中...' : '⏳ 加载中...'; frame.src = hkLocalUrl || decodedUrl; } } function closePdf() { if (pdfTimeout) { clearTimeout(pdfTimeout); pdfTimeout=null; } $('#pdfOverlay').classList.remove('open'); $('#pdfFrame').src = ''; } // ══════════════════ SEARCH ════════════════════════════ function searchPapers(q) { q=(q||'').toLowerCase().trim(); if (q.length<2) { $$('.card').forEach(c=>c.style.outline=''); return; } fetch(`${API}/papers?q=${encodeURIComponent(q)}&limit=10`).then(r=>r.json()).then(ps=>{ const matched = new Set(ps.map(p=>p.module_id)); $$('.card').forEach(c=>{ const m=[...c.classList].find(x=>x.startsWith('card-'))?.replace('card-',''); c.style.outline=matched.has(m)?'2px solid var(--green)':''; }); }); } // ══════════════════ EVENTS ════════════════════════════ if (typeof document !== 'undefined') { document.addEventListener('DOMContentLoaded', () => { init(); $('.search-input').addEventListener('input', e => searchPapers(e.target.value)); $('#modalClose').addEventListener('click', closeModal); $('#overlay').addEventListener('click', e => { if (e.target===$('#overlay')) closeModal(); }); }); }