ai-research-survey

Systematic scan of agentic development research. What's signal, what's noise.
git clone https://git.shiptheloop.com/ai-research-survey.git
Log | Files | Refs

paper-detail.ts (8643B)


      1 import { loadPaperDetail, loadNetwork } from '../data';
      2 
      3 function scoreColor(s: number): string {
      4   if (s < 30) return 'var(--red)';
      5   if (s < 50) return 'var(--yellow)';
      6   if (s < 70) return 'var(--accent)';
      7   return 'var(--green)';
      8 }
      9 
     10 function formatCatName(name: string): string {
     11   return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
     12 }
     13 
     14 function formatQName(name: string): string {
     15   return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
     16 }
     17 
     18 export async function renderPaperDetail(app: HTMLElement, slug: string) {
     19   app.innerHTML = '<div class="spinner"></div>';
     20 
     21   let paper;
     22   try {
     23     paper = await loadPaperDetail(slug);
     24   } catch {
     25     app.innerHTML = `<p>Paper not found: ${slug}</p><a class="back-link" href="#/papers">Back to papers</a>`;
     26     return;
     27   }
     28 
     29   if (!paper || !paper.id) {
     30     app.innerHTML = `<p>Paper not found: ${slug}</p><a class="back-link" href="#/papers">Back to papers</a>`;
     31     return;
     32   }
     33 
     34   // Group checklist by category
     35   const categories = new Map<string, typeof paper.checklist>();
     36   for (const item of paper.checklist) {
     37     if (!categories.has(item.category)) categories.set(item.category, []);
     38     categories.get(item.category)!.push(item);
     39   }
     40 
     41   // External links
     42   const links: string[] = [];
     43   if (paper.arxiv_id) {
     44     links.push(`<a href="https://arxiv.org/abs/${paper.arxiv_id}" target="_blank" rel="noopener">arXiv</a>`);
     45     links.push(`<a href="https://arxiv.org/pdf/${paper.arxiv_id}" target="_blank" rel="noopener">PDF</a>`);
     46   }
     47   if (paper.doi) {
     48     links.push(`<a href="https://doi.org/${paper.doi}" target="_blank" rel="noopener">DOI</a>`);
     49   }
     50   if (paper.source_url && !paper.source_url.includes('arxiv.org')) {
     51     links.push(`<a href="${paper.source_url}" target="_blank" rel="noopener">Source</a>`);
     52   }
     53   if (paper.code_url) {
     54     links.push(`<a href="${paper.code_url}" target="_blank" rel="noopener">Code</a>`);
     55   }
     56 
     57   // HN threads
     58   const hnThreads = (paper as any).hn_threads as { hn_id: string; title: string; points: number; comments: number; url: string }[] | undefined;
     59   if (hnThreads && hnThreads.length > 0) {
     60     const top = hnThreads[0];
     61     links.push(`<a href="${top.url}" target="_blank" rel="noopener">HN (${top.points}pts)</a>`);
     62   }
     63 
     64   // Load network for citations (lazy, non-blocking)
     65   let incomingHtml = '';
     66   let outgoingHtml = '';
     67   try {
     68     const net = await loadNetwork();
     69     const incoming = net.edges.filter(e => e[1] === slug).map(e => e[0]);
     70     const outgoing = net.edges.filter(e => e[0] === slug).map(e => e[1]);
     71     const nodeMap = new Map(net.nodes.map(n => [n.id, n]));
     72 
     73     if (incoming.length) {
     74       incomingHtml = `<h3 style="font-size:0.85rem;color:var(--text-dim);margin:0.5rem 0">Cited by (${incoming.length})</h3>
     75         ${incoming.map(id => {
     76           const n = nodeMap.get(id);
     77           return `<div style="font-size:0.82rem;padding:0.2rem 0"><a href="#/paper/${id}" style="color:var(--accent);text-decoration:none">${n?.title || id}</a>${n?.score != null ? ` <span style="color:${scoreColor(n.score)};font-family:var(--font);font-size:0.75rem">${n.score}%</span>` : ''}</div>`;
     78         }).join('')}`;
     79     }
     80     if (outgoing.length) {
     81       outgoingHtml = `<h3 style="font-size:0.85rem;color:var(--text-dim);margin:0.5rem 0">Cites (${outgoing.length})</h3>
     82         ${outgoing.map(id => {
     83           const n = nodeMap.get(id);
     84           return `<div style="font-size:0.82rem;padding:0.2rem 0"><a href="#/paper/${id}" style="color:var(--accent);text-decoration:none">${n?.title || id}</a>${n?.score != null ? ` <span style="color:${scoreColor(n.score)};font-family:var(--font);font-size:0.75rem">${n.score}%</span>` : ''}</div>`;
     85         }).join('')}`;
     86     }
     87   } catch { /* network data optional */ }
     88 
     89   app.innerHTML = `
     90     <a class="back-link" href="#/papers">\u2190 Back to papers</a>
     91     <div class="paper-header">
     92       <h2>${paper.title}</h2>
     93       <div class="paper-meta">
     94         <span style="color:${scoreColor(paper.score!)};font-family:var(--font);font-weight:700">${paper.score}%</span>
     95         <span>${paper.year || ''}</span>
     96         <span>${paper.venue || ''}</span>
     97         <span class="archetype ${paper.archetype}">${paper.archetype}</span>
     98         ${paper.tags.map(t => `<span class="tag">${t}</span>`).join('')}
     99       </div>
    100       ${links.length ? `<div class="paper-links">${links.join('')}</div>` : ''}
    101     </div>
    102 
    103     ${paper.key_findings ? `<div class="section"><h2>Key Findings</h2><p style="font-size:0.9rem">${paper.key_findings}</p></div>` : ''}
    104 
    105     ${paper.games.length ? `<div class="section"><h2>Named Games</h2>${paper.games.map(g => `<span class="tag" style="background:rgba(240,101,101,0.15);color:var(--red);margin-right:0.5rem">${g}</span>`).join('')}</div>` : ''}
    106 
    107     ${renderEngagementFactors(paper)}
    108 
    109     <div class="detail-grid">
    110       <div class="section">
    111         <h2>Checklist</h2>
    112         ${Array.from(categories.entries()).map(([cat, items]) => `
    113           <div class="checklist-category">
    114             <h3>${formatCatName(cat)} ${paper.category_scores[cat] != null ? `<span style="color:${scoreColor(paper.category_scores[cat])};font-size:0.8rem">${paper.category_scores[cat]}%</span>` : ''}</h3>
    115             ${items.map(item => {
    116               const icon = !item.applies ? '\u2014' : item.answer ? '\u2713' : '\u2717';
    117               const cls = !item.applies ? 'na' : item.answer ? 'pass' : 'fail';
    118               return `<div class="checklist-item">
    119                 <span class="checklist-icon ${cls}">${icon}</span>
    120                 <div class="checklist-q">
    121                   ${formatQName(item.question)}
    122                   <div class="checklist-justification">${item.justification}</div>
    123                 </div>
    124               </div>`;
    125             }).join('')}
    126           </div>
    127         `).join('')}
    128       </div>
    129 
    130       <div>
    131         ${paper.claims.length ? `<div class="section">
    132           <h2>Claims (${paper.claims.length})</h2>
    133           ${paper.claims.map(c => `<div class="claim">
    134             <span class="support-badge ${c.supported}">${c.supported}</span>
    135             <span class="claim-text"> ${c.claim}</span>
    136           </div>`).join('')}
    137         </div>` : ''}
    138 
    139         ${paper.red_flags.length ? `<div class="section">
    140           <h2>Red Flags (${paper.red_flags.length})</h2>
    141           ${paper.red_flags.map(r => `<div class="red-flag">
    142             <div class="flag-name">${r.flag}</div>
    143             <div class="flag-detail">${r.detail}</div>
    144           </div>`).join('')}
    145         </div>` : ''}
    146 
    147         ${incomingHtml || outgoingHtml ? `<div class="section">
    148           <h2>Internal Citations</h2>
    149           ${incomingHtml}${outgoingHtml}
    150         </div>` : ''}
    151 
    152         <div class="section">
    153           <h2>Category Scores</h2>
    154           ${Object.entries(paper.category_scores).sort((a, b) => b[1] - a[1]).map(([cat, val]) => `
    155             <div class="hbar">
    156               <div class="hbar-label"><span>${formatCatName(cat)}</span><span>${val}%</span></div>
    157               <div class="hbar-track"><div class="hbar-fill" style="width:${val}%;background:${scoreColor(val)}"></div></div>
    158             </div>
    159           `).join('')}
    160         </div>
    161       </div>
    162     </div>
    163   `;
    164 }
    165 
    166 const EF_LABELS: Record<string, string> = {
    167   practical_relevance: 'Practical Relevance',
    168   surprise_contrarian: 'Surprise / Contrarian',
    169   fear_safety: 'Fear / Safety',
    170   drama_conflict: 'Drama / Conflict',
    171   demo_ability: 'Demo-ability',
    172   brand_recognition: 'Brand Recognition',
    173 };
    174 
    175 const EF_ORDER = ['practical_relevance', 'surprise_contrarian', 'fear_safety',
    176                   'drama_conflict', 'demo_ability', 'brand_recognition'];
    177 
    178 function renderEngagementFactors(paper: any): string {
    179   const ef = paper.engagement_factors;
    180   if (!ef) return '';
    181 
    182   return `<div class="section">
    183     <h2>Engagement Factors (v3)</h2>
    184     <p style="font-size:0.8rem;color:var(--text-dim);margin-bottom:0.75rem">What drives social/media attention for this paper (0-3 scale).</p>
    185     ${EF_ORDER.map(dim => {
    186       const d = ef[dim];
    187       if (!d) return '';
    188       const pct = d.score / 3 * 100;
    189       const color = d.score === 0 ? 'var(--gray)' : d.score === 1 ? 'var(--yellow)' : d.score === 2 ? 'var(--accent)' : 'var(--green)';
    190       return `<div class="hbar">
    191         <div class="hbar-label"><span>${EF_LABELS[dim] || dim} <span style="color:var(--text-dim);font-size:0.75rem">${d.score}/3</span></span></div>
    192         <div class="hbar-track"><div class="hbar-fill" style="width:${pct}%;background:${color}"></div></div>
    193         <div style="font-size:0.78rem;color:var(--text-dim);margin-top:1px">${d.justification}</div>
    194       </div>`;
    195     }).join('')}
    196   </div>`;
    197 }

Impressum · Datenschutz