import gradio as gr import requests from datetime import datetime, timezone API_URL = "https://huggingface.co/api/daily_papers" class PaperManager: def __init__(self, papers_per_page=10): self.papers_per_page = papers_per_page self.current_page = 1 self.papers = [] self.total_pages = 1 def fetch_papers(self): try: response = requests.get(f"{API_URL}?limit=50") response.raise_for_status() data = response.json() # Sort papers primarily by 'publishedAt' descending, then by 'upvotes' descending self.papers = sorted( data, key=lambda x: ( datetime.fromisoformat( x.get('publishedAt', datetime.now(timezone.utc).isoformat()).replace('Z', '+00:00') ), x.get('paper', {}).get('upvotes', 0) ), reverse=True # Ensures descending order ) self.total_pages = (len(self.papers) + self.papers_per_page - 1) // self.papers_per_page self.current_page = 1 return True except requests.RequestException as e: print(f"Error fetching papers: {e}") return False except Exception as e: print(f"Unexpected error: {e}") return False def format_paper(self, paper): title = paper.get('title', 'No title') paper_id = paper.get('paper', {}).get('id', '') url = f"https://huggingface.co/papers/{paper_id}" authors = ', '.join([author.get('name', '') for author in paper.get('paper', {}).get('authors', [])]) upvotes = paper.get('paper', {}).get('upvotes', 0) comments = paper.get('numComments', 0) published_time = datetime.fromisoformat( paper.get('publishedAt', datetime.now(timezone.utc).isoformat()).replace('Z', '+00:00') ) time_ago_days = (datetime.now(timezone.utc) - published_time).days return f""" <div class="paper-item"> <h3><a href="{url}" target="_blank">{title}</a></h3> <p class="paper-meta"> <span class="upvotes">{upvotes} ▲</span> <span class="comments">{comments}💬</span> <span class="authors">{authors}</span> <span class="time-ago">{time_ago_days}d</span> </p> </div> """ def render_papers(self): start = (self.current_page - 1) * self.papers_per_page end = start + self.papers_per_page current_papers = self.papers[start:end] if not current_papers: return "<div class='no-papers'>No papers available for this page.</div>" return "".join([self.format_paper(paper) for paper in current_papers]) def next_page(self): if self.current_page < self.total_pages: self.current_page += 1 return self.render_papers(), f"Page {self.current_page} of {self.total_pages}" def prev_page(self): if self.current_page > 1: self.current_page -= 1 return self.render_papers(), f"Page {self.current_page} of {self.total_pages}" css = """ /* Your existing CSS */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; background-color: #f0f2f5; margin: 0; padding: 0; } .container { max-width: 100%; margin: 0 auto; background-color: white; padding: 1rem; } .title { text-align: center; color: #1a202c; font-size: 1.5rem; margin-bottom: 1rem; } .paper-list { max-height: 60vh; overflow-y: auto; border: 1px solid #e2e8f0; border-radius: 8px; padding: 0.5rem; } .paper-item { border-bottom: 1px solid #e2e8f0; padding: 0.5rem 0; } .paper-item:last-child { border-bottom: none; } .paper-item h3 { margin: 0 0 0.25rem 0; font-size: 1rem; } .paper-item a { color: #2b6cb0; text-decoration: none; font-weight: 600; } .paper-item a:hover { text-decoration: underline; } .paper-meta { font-size: 0.75rem; color: #4a5568; display: flex; flex-wrap: wrap; gap: 0.5rem; } .footer { display: flex; justify-content: space-between; align-items: center; margin-top: 0.5rem; position: relative; flex-wrap: wrap; gap: 0.5rem; } /* Target Gradio button elements more specifically */ #component-5, #component-7, #component-9 { background-color: #3498db !important; /* Mobile-friendly blue */ color: white !important; border: none !important; padding: 0.5rem 1rem !important; border-radius: 4px !important; cursor: pointer !important; transition: background-color 0.3s !important; font-size: 0.875rem !important; line-height: 1.25rem !important; width: auto !important; max-width: 100% !important; } #component-5:hover, #component-7:hover, #component-9:hover { background-color: #2980b9 !important; /* Darker blue on hover */ } /* Style for the refresh button */ #component-11 { background-color: #2ecc71 !important; /* More visible green */ color: white !important; border: none !important; padding: 0.5rem !important; border-radius: 4px !important; cursor: pointer !important; transition: background-color 0.3s !important; font-size: 1rem !important; width: auto !important; max-width: 100% !important; height: auto !important; line-height: 1 !important; } #component-11:hover { background-color: #27ae60 !important; /* Darker green on hover */ } /* Make sure the page info doesn't break the layout */ #component-8 { flex: 1; text-align: center; min-width: 100px; } .no-papers { text-align: center; color: #718096; padding: 1rem; } /* Responsive adjustments */ @media (max-width: 640px) { #component-5, #component-7, #component-9, #component-11 { font-size: 0.75rem !important; padding: 0.4rem 0.8rem !important; } .footer { justify-content: center; } } @media (min-width: 640px) { .container { max-width: 640px; padding: 1.5rem; } .title { font-size: 1.75rem; } .paper-item h3 { font-size: 1.125rem; } .paper-meta { font-size: 0.875rem; } } @media (min-width: 768px) { .container { max-width: 768px; } .footer { position: sticky; bottom: 0; background-color: white; } } """ paper_manager = PaperManager() def initialize_app(): if paper_manager.fetch_papers(): return paper_manager.render_papers(), f"Page {paper_manager.current_page} of {paper_manager.total_pages}" else: return "<div class='no-papers'>Failed to fetch papers. Please try again later.</div>", "Error" def refresh_papers(): if paper_manager.fetch_papers(): return paper_manager.render_papers(), f"Page {paper_manager.current_page} of {paper_manager.total_pages}" else: return "<div class='no-papers'>Failed to refresh papers. Please try again later.</div>", "Error" demo = gr.Blocks(css=css) with demo: with gr.Column(elem_classes=["container"]): gr.Markdown("# Daily Papers - Hacker News Style", elem_classes=["title"]) refresh_button = gr.Button("↻ Refresh", variant="primary", elem_classes=["refresh-btn"]) with gr.Row(elem_classes=["footer"]): prev_button = gr.Button("← Prev") page_info = gr.Markdown(elem_classes=["page-info"]) next_button = gr.Button("Next →") paper_list = gr.HTML(elem_classes=["paper-list"]) demo.load(initialize_app, outputs=[paper_list, page_info]) refresh_button.click(refresh_papers, outputs=[paper_list, page_info]) prev_button.click(paper_manager.prev_page, outputs=[paper_list, page_info]) next_button.click(paper_manager.next_page, outputs=[paper_list, page_info]) demo.launch()