DVampire
update
21fd477
raw
history blame
16.4 kB
// Theme Management
class ThemeManager {
constructor() {
this.theme = localStorage.getItem('theme') || 'light';
this.init();
}
init() {
document.documentElement.setAttribute('data-theme', this.theme);
this.updateThemeIcon();
}
toggle() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', this.theme);
localStorage.setItem('theme', this.theme);
this.updateThemeIcon();
}
updateThemeIcon() {
const lightIcon = document.querySelector('.light-icon');
const darkIcon = document.querySelector('.dark-icon');
if (this.theme === 'light') {
lightIcon.style.display = 'block';
darkIcon.style.display = 'none';
} else {
lightIcon.style.display = 'none';
darkIcon.style.display = 'block';
}
}
}
// Date Management
class DateManager {
constructor() {
// Start with today's date, but it will be updated when we get the actual available date
this.currentDate = new Date();
this.init();
}
init() {
this.updateDateDisplay();
this.bindEvents();
}
formatDate(date) {
const options = {
year: 'numeric',
month: 'short',
day: 'numeric'
};
return date.toLocaleDateString('en-US', options);
}
updateDateDisplay() {
const dateDisplay = document.getElementById('dateDisplay');
dateDisplay.textContent = this.formatDate(this.currentDate);
}
navigateDate(direction) {
const newDate = new Date(this.currentDate);
newDate.setDate(newDate.getDate() + direction);
this.currentDate = newDate;
this.updateDateDisplay();
this.loadDaily();
}
bindEvents() {
document.getElementById('prevDate').addEventListener('click', () => {
this.navigateDate(-1);
});
document.getElementById('nextDate').addEventListener('click', () => {
this.navigateDate(1);
});
}
getDateString() {
const pad = (n) => String(n).padStart(2, '0');
return `${this.currentDate.getFullYear()}-${pad(this.currentDate.getMonth()+1)}-${pad(this.currentDate.getDate())}`;
}
}
// Search Management
class SearchManager {
constructor() {
this.init();
}
init() {
this.bindEvents();
}
bindEvents() {
const searchInput = document.querySelector('.search-input');
const aiSearchInput = document.querySelector('.ai-search-input');
searchInput.addEventListener('input', (e) => {
this.handleSearch(e.target.value);
});
aiSearchInput.addEventListener('input', (e) => {
this.handleAISearch(e.target.value);
});
}
handleSearch(query) {
// Implement search functionality
console.log('Search query:', query);
}
handleAISearch(query) {
// Implement AI search functionality
console.log('AI search query:', query);
}
}
// Paper Card Renderer
class PaperCardRenderer {
constructor() {
this.cardsContainer = document.getElementById('cards');
}
generateThumbnail(title) {
// Generate a simple thumbnail based on title
const canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 120;
const ctx = canvas.getContext('2d');
// Create gradient background
const gradient = ctx.createLinearGradient(0, 0, 400, 120);
gradient.addColorStop(0, '#3b82f6');
gradient.addColorStop(1, '#06b6d4');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 400, 120);
// Add text
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.font = 'bold 16px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const words = title.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
const testLine = currentLine + word + ' ';
const metrics = ctx.measureText(testLine);
if (metrics.width > 350 && currentLine !== '') {
lines.push(currentLine);
currentLine = word + ' ';
} else {
currentLine = testLine;
}
}
lines.push(currentLine);
const yStart = 60 - (lines.length * 20) / 2;
lines.forEach((line, index) => {
ctx.fillText(line.trim(), 200, yStart + index * 20);
});
return canvas.toDataURL();
}
generateAuthorAvatars(authorCount) {
const avatars = [];
const count = Math.min(authorCount, 5);
for (let i = 0; i < count; i++) {
avatars.push(`<li title="Author ${i + 1}"></li>`);
}
return avatars.join('');
}
renderCard(paper) {
const title = paper.title || 'Untitled Paper';
const abstract = paper.abstract || 'No abstract available';
const authors = paper.authors || [];
const authorCount = paper.author_count || authors.length || 0;
const upvotes = paper.upvotes || 0;
const githubStars = paper.github_stars || 0;
const comments = paper.comments || 0;
const submitter = paper.submitter || 'Anonymous';
// Generate thumbnail URL - try to use HF thumbnail if available
const arxivId = paper.arxiv_id;
const thumbnailUrl = arxivId ?
`https://cdn-thumbnails.huggingface.co/social-thumbnails/papers/${arxivId}.png` :
this.generateThumbnail(title);
const authorAvatars = this.generateAuthorAvatars(authorCount);
const card = document.createElement('article');
card.className = 'hf-paper-card';
card.innerHTML = `
<a href="${paper.huggingface_url || '#'}" class="paper-thumbnail-link" target="_blank" rel="noreferrer">
<img src="${thumbnailUrl}" loading="lazy" decoding="async" alt="" class="paper-thumbnail-img">
</a>
<div class="submitted-by-badge">
<span>Submitted by</span>
<div class="submitter-avatar-placeholder">
<i class="fas fa-user"></i>
</div>
${submitter}
</div>
<div class="card-content">
<div class="upvote-section">
<label class="upvote-button">
<input type="checkbox" class="upvote-checkbox">
<svg class="upvote-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 12 12">
<path fill="currentColor" d="M5.19 2.67a.94.94 0 0 1 1.62 0l3.31 5.72a.94.94 0 0 1-.82 1.4H2.7a.94.94 0 0 1-.82-1.4l3.31-5.7v-.02Z"></path>
</svg>
<div class="upvote-count">${upvotes}</div>
</label>
</div>
<div class="paper-info">
<h3 class="paper-title">
<a href="${paper.huggingface_url || '#'}" class="title-link">
${title}
</a>
</h3>
<div class="paper-meta">
<div class="authors-section">
<a href="${paper.huggingface_url || '#'}" class="authors-link">
<ul class="author-avatars-list">
${authorAvatars}
</ul>
<div class="author-count">· ${authorCount} authors</div>
</a>
</div>
<div class="engagement-metrics">
<a href="${paper.huggingface_url || '#'}" class="metric-link">
<svg class="github-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="img" width="1.03em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 250">
<path d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0zm-80.06 182.34c-.282.636-1.283.827-2.194.39c-.929-.417-1.45-1.284-1.15-1.922c.276-.655 1.279-.838 2.205-.399c.93.418 1.46 1.293 1.139 1.931zm6.296 5.618c-.61.566-1.804.303-2.614-.591c-.837-.892-.994-2.086-.375-2.66c.63-.566 1.787-.301 2.626.591c.838.903 1 2.088.363 2.66zm4.32 7.188c-.785.545-2.067.034-2.86-1.104c-.784-1.138-.784-2.503.017-3.05c.795-.547 2.058-.055 2.861 1.075c.782 1.157.782 2.522-.019 3.08zm7.304 8.325c-.701.774-2.196.566-3.29-.49c-1.119-1.032-1.43-2.496-.726-3.27c.71-.776 2.213-.558 3.315.49c1.11 1.03 1.45 2.505.701 3.27zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033c-1.448-.439-2.395-1.613-2.103-2.626c.301-1.01 1.747-1.484 3.207-1.028c1.446.436 2.396 1.602 2.095 2.622zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95c-1.53.034-2.769-.82-2.786-1.86c0-1.065 1.202-1.932 2.733-1.958c1.522-.03 2.768.818 2.768 1.868zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37c-1.485.271-2.861-.365-3.05-1.386c-.184-1.056.893-2.114 2.376-2.387c1.514-.263 2.868.356 3.061 1.403z" fill="currentColor"></path>
</svg>
<span>${githubStars}</span>
</a>
<a href="${paper.huggingface_url || '#'}" class="metric-link">
<svg class="comment-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<span>${comments}</span>
</a>
</div>
</div>
</div>
</div>
${paper.arxiv_id ? `
<div class="card-actions">
<a href="/paper.html?id=${encodeURIComponent(paper.arxiv_id)}" class="eval-button">
<i class="fas fa-chart-line"></i>Evaluation
</a>
</div>
` : ''}
`;
return card;
}
renderCards(papers) {
this.cardsContainer.innerHTML = '';
if (!papers || papers.length === 0) {
this.cardsContainer.innerHTML = `
<div style="grid-column: 1 / -1; text-align: center; padding: 48px; color: var(--text-muted);">
<i class="fas fa-search" style="font-size: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<h3>No papers found</h3>
<p>Try selecting a different date or check back later.</p>
</div>
`;
return;
}
papers.forEach(paper => {
const card = this.renderCard(paper);
this.cardsContainer.appendChild(card);
});
}
}
// Main Application
class PaperIndexApp {
constructor() {
this.themeManager = new ThemeManager();
this.dateManager = new DateManager();
this.searchManager = new SearchManager();
this.cardRenderer = new PaperCardRenderer();
this.init();
}
init() {
this.bindEvents();
this.loadDaily();
}
bindEvents() {
// Theme toggle
document.getElementById('themeToggle').addEventListener('click', () => {
this.themeManager.toggle();
});
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
});
});
}
async loadDaily() {
const dateStr = this.dateManager.getDateString();
try {
const response = await fetch(`/api/daily?date_str=${encodeURIComponent(dateStr)}`);
if (!response.ok) {
throw new Error('Failed to load daily papers');
}
const data = await response.json();
console.log('API Response:', {
requested_date: data.requested_date,
actual_date: data.date,
fallback_used: data.fallback_used,
cards_count: data.cards?.length
});
// Update the date display if a fallback was used or if we got a different date than requested
if (data.date && data.requested_date && data.date !== data.requested_date) {
console.log('Updating date display from', data.requested_date, 'to', data.date);
const fallbackDate = new Date(data.date);
this.dateManager.currentDate = fallbackDate;
this.dateManager.updateDateDisplay();
// Show a notification about the fallback
this.showFallbackNotification(data.requested_date, data.date);
}
// Show cache status if available
if (data.cached) {
this.showCacheNotification(data.cached_at);
}
this.cardRenderer.renderCards(data.cards || []);
} catch (error) {
console.error('Error loading papers:', error);
this.cardRenderer.renderCards([]);
// Show fallback message
this.cardRenderer.cardsContainer.innerHTML = `
<div style="grid-column: 1 / -1; text-align: center; padding: 48px; color: var(--text-muted);">
<i class="fas fa-exclamation-triangle" style="font-size: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<h3>Unable to load papers</h3>
<p>Backend unavailable on static hosting. Try opening the daily page on Hugging Face:</p>
<a class="action-btn primary" href="https://huggingface.co/papers/date/${encodeURIComponent(dateStr)}" target="_blank" rel="noreferrer">
<i class="fas fa-external-link-alt"></i>Open on Hugging Face
</a>
</div>
`;
}
}
showFallbackNotification(requestedDate, actualDate) {
// Create a temporary notification
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: var(--bg-primary);
border: 1px solid var(--border-medium);
border-radius: 8px;
padding: 16px;
box-shadow: var(--shadow-lg);
z-index: 1000;
max-width: 300px;
color: var(--text-primary);
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
<i class="fas fa-info-circle" style="color: var(--accent-primary);"></i>
<strong>Date Updated</strong>
</div>
<p style="margin: 0; font-size: 14px; color: var(--text-secondary);">
Papers for ${requestedDate} not available. Showing latest available: ${actualDate}
</p>
`;
document.body.appendChild(notification);
// Remove notification after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 5000);
}
showCacheNotification(cachedAt) {
// Create a temporary notification
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: var(--bg-primary);
border: 1px solid var(--border-medium);
border-radius: 8px;
padding: 16px;
box-shadow: var(--shadow-lg);
z-index: 1000;
max-width: 300px;
color: var(--text-primary);
`;
const cacheTime = new Date(cachedAt).toLocaleString();
notification.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
<i class="fas fa-database" style="color: var(--accent-success);"></i>
<strong>Cached Data</strong>
</div>
<p style="margin: 0; font-size: 14px; color: var(--text-secondary);">
Showing cached data from ${cacheTime}
</p>
`;
document.body.appendChild(notification);
// Remove notification after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new PaperIndexApp();
});