import gradio as gr import requests from datetime import datetime, timezone import logging # Configure logging for debugging purposes logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') API_URL = "https://huggingface.co/api/daily_papers" class PaperManager: def __init__(self, papers_per_page=10, max_papers=100): self.papers_per_page = papers_per_page self.max_papers = max_papers self.cache = [] self.last_fetch_date = None self.total_pages = 1 def fetch_papers(self): """ Fetch the first 100 most recent papers from the API, sorted by upvotes descending. Caches the result and fetches only once a day. Returns: tuple: (success: bool, data: list, error_message: str) """ today = datetime.now(timezone.utc).date() if self.last_fetch_date == today and self.cache: logging.info("Using cached papers.") return True, self.cache, "" else: try: # Fetch up to max_papers; adjust 'limit' as per API's capability response = requests.get(f"{API_URL}?page=1&limit={self.max_papers}") response.raise_for_status() data = response.json() if not isinstance(data, list): raise ValueError("API response is not a list of papers.") # Sort papers by upvotes descending sorted_papers = sorted( data, key=lambda x: x.get('paper', {}).get('upvotes', 0), reverse=True ) # Limit to max_papers self.cache = sorted_papers[:self.max_papers] self.last_fetch_date = today self.total_pages = (len(self.cache) + self.papers_per_page - 1) // self.papers_per_page logging.info(f"Fetched {len(self.cache)} papers. Total pages: {self.total_pages}") return True, self.cache, "" except requests.HTTPError as http_err: error_message = f"HTTP error occurred: {http_err}" logging.error(error_message) return False, [], error_message except requests.ConnectionError as conn_err: error_message = f"Connection error occurred: {conn_err}" logging.error(error_message) return False, [], error_message except requests.Timeout as timeout_err: error_message = f"Timeout error occurred: {timeout_err}" logging.error(error_message) return False, [], error_message except requests.RequestException as req_err: error_message = f"An error occurred: {req_err}" logging.error(error_message) return False, [], error_message except ValueError as val_err: error_message = f"Data format error: {val_err}" logging.error(error_message) return False, [], error_message def format_paper(self, paper): """ Format a single paper's information into HTML. """ title = paper.get('title', 'No title') paper_id = paper.get('paper', {}).get('id', '') url = f"https://huggingface.co/papers/{paper_id}" if paper_id else "#" authors = ', '.join([author.get('name', 'Unknown') for author in paper.get('paper', {}).get('authors', [])]) upvotes = paper.get('paper', {}).get('upvotes', 0) comments = paper.get('numComments', 0) published_at = paper.get('publishedAt', datetime.now(timezone.utc).isoformat()) try: published_time = datetime.fromisoformat(published_at.replace('Z', '+00:00')) time_ago = (datetime.now(timezone.utc) - published_time).days except ValueError: time_ago = "Unknown" return f"""
{title}
{upvotes} upvotes | by {authors} | {time_ago} days ago | {comments} comments
""" def render_papers(self, papers, page=1): """ Render HTML for a list of papers based on the current page. """ if not papers: return "
No papers available for this page.
" start = (page - 1) * self.papers_per_page end = start + self.papers_per_page page_papers = papers[start:end] return "".join([self.format_paper(paper) for paper in page_papers]) def search_papers(self, query): """ Filter papers based on the search query. """ if not query: return self.cache return [paper for paper in self.cache if query.lower() in paper.get('title', '').lower()] css = """ html, body { height: 100%; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; background-color: #f0f0f0; } .container { font-family: Arial, sans-serif; max-width: 800px; width: 100%; background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } .paper-list { max-height: 400px; overflow-y: auto; border: 1px solid #eee; border-radius: 5px; padding: 10px; margin-bottom: 10px; } .search-row { display: flex; gap: 10px; margin-bottom: 20px; } .title { text-align: center; color: #333; } .footer { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; } .error-message { color: red; margin-bottom: 10px; text-align: center; } """ # Instantiate the PaperManager paper_manager = PaperManager() def initialize(): """ Initialize the app by fetching papers and setting initial states. """ success, papers, error_message = paper_manager.fetch_papers() if success: page = 1 total_pages = paper_manager.total_pages page_papers = paper_manager.render_papers(papers, page) page_info_text = f"Page {page} of {total_pages}" return page_papers, page, total_pages, papers, page_info_text, "" else: error_html = f"
Error fetching papers: {error_message}
" return error_html, 1, 1, [], "Page 1 of 1", error_message def refresh_papers(current_page, query): """ Refresh the papers. Fetch new data if a new day has started. """ success, papers, error_message = paper_manager.fetch_papers() if success: # Reset to first page on refresh new_page = 1 if query: filtered = paper_manager.search_papers(query) total_pages = (len(filtered) + paper_manager.papers_per_page - 1) // paper_manager.papers_per_page if filtered else 1 page_papers = paper_manager.render_papers(filtered, new_page) page_info_text = f"Page {new_page} of {total_pages}" return page_papers, new_page, total_pages, filtered, page_info_text, "" else: papers = paper_manager.cache total_pages = paper_manager.total_pages page_papers = paper_manager.render_papers(papers, new_page) page_info_text = f"Page {new_page} of {total_pages}" return page_papers, new_page, total_pages, papers, page_info_text, "" else: error_html = f"
Error fetching papers: {error_message}
" return error_html, current_page, paper_manager.total_pages, [], f"Page {current_page} of {paper_manager.total_pages}", error_message def search_papers(query, papers): """ Search for papers based on the query and update the display. """ if query: filtered = paper_manager.search_papers(query) else: filtered = papers total_pages = (len(filtered) + paper_manager.papers_per_page - 1) // paper_manager.papers_per_page if filtered else 1 page = 1 page_papers = paper_manager.render_papers(filtered, page) page_info_text = f"Page {page} of {total_pages}" if not filtered: error_html = "
No papers match your search query.
" else: error_html = "" return page_papers, page, total_pages, filtered, page_info_text, error_html def change_page(direction, current_page, total_pages, papers, query): """ Change the current page based on the direction ('next' or 'prev'). """ if direction == "next" and current_page < total_pages: new_page = current_page + 1 elif direction == "prev" and current_page > 1: new_page = current_page - 1 else: new_page = current_page # No change if limits are reached if not papers: page_papers = "
No papers available for this page.
" else: page_papers = paper_manager.render_papers(papers, new_page) page_info_text = f"Page {new_page} of {total_pages}" return page_papers, new_page, total_pages, papers, page_info_text, "" def go_prev(current_page, total_pages, papers, query): """ Handle the 'Previous Page' button click. """ return change_page("prev", current_page, total_pages, papers, query) def go_next(current_page, total_pages, papers, query): """ Handle the 'Next Page' button click. """ return change_page("next", current_page, total_pages, papers, query) # Define the Gradio Blocks interface demo = gr.Blocks(css=css) with demo: with gr.Column(elem_classes=["container"]): # Title gr.Markdown("# Daily Papers - HackerNews Style", elem_classes=["title"]) # Search and Refresh Row with gr.Row(elem_classes=["search-row"]): search_input = gr.Textbox(label="Search papers", placeholder="Enter search term...") refresh_button = gr.Button("Refresh") # Error Message Display error_display = gr.HTML(elem_classes=["error-message"]) # Paper List Display paper_list = gr.HTML(elem_classes=["paper-list"]) # Pagination Controls with gr.Row(elem_classes=["footer"]): prev_button = gr.Button("Previous Page") page_info = gr.Markdown("Page 1 of 1") next_button = gr.Button("Next Page") # Hidden States current_page_state = gr.State(1) total_pages_state = gr.State(1) papers_state = gr.State([]) # Initialize the app on load demo.load( initialize, outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) # Search Functionality search_input.submit( search_papers, inputs=[search_input, papers_state], outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) search_input.change( search_papers, inputs=[search_input, papers_state], outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) # Refresh Functionality refresh_button.click( refresh_papers, inputs=[current_page_state, search_input], outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) # Pagination Buttons prev_button.click( go_prev, inputs=[current_page_state, total_pages_state, papers_state, search_input], outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) next_button.click( go_next, inputs=[current_page_state, total_pages_state, papers_state, search_input], outputs=[paper_list, current_page_state, total_pages_state, papers_state, page_info, error_display] ) # Launch the Gradio app demo.launch()