Spaces:
Runtime error
Runtime error
Create app.py
Browse filesAdded a functionality to allow the user to choose between hot papers (computed using hot score) and top papers, which chooses the papers with the most upvotes in a certain time frame (day/week/month year)
Note
1. I wasn't able to run locally https://huggingface.co/api/daily_papers?limit={} with a value large than 100. Please validate it works with a 1000
2. We might need to significantly increase the number of fetched papers to retrieve the top papers from the last year, or we could design some caching mechanism.
app.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import gradio as gr
|
2 |
import requests
|
3 |
-
from datetime import datetime, timezone
|
4 |
|
5 |
API_URL = "https://huggingface.co/api/daily_papers"
|
6 |
|
@@ -10,8 +10,10 @@ class PaperManager:
|
|
10 |
self.current_page = 1
|
11 |
self.papers = []
|
12 |
self.total_pages = 1
|
|
|
|
|
13 |
|
14 |
-
def
|
15 |
"""
|
16 |
Calculate the score of a paper based on upvotes and age.
|
17 |
This mimics the "hotness" algorithm used by platforms like Hacker News.
|
@@ -21,28 +23,53 @@ class PaperManager:
|
|
21 |
try:
|
22 |
published_time = datetime.fromisoformat(published_at_str.replace('Z', '+00:00'))
|
23 |
except ValueError:
|
24 |
-
# If parsing fails, use current time to minimize the impact on sorting
|
25 |
published_time = datetime.now(timezone.utc)
|
26 |
|
27 |
time_diff = datetime.now(timezone.utc) - published_time
|
28 |
-
time_diff_hours = time_diff.total_seconds() / 3600
|
29 |
|
30 |
-
# Avoid division by zero and apply the hotness formula
|
31 |
score = upvotes / ((time_diff_hours + 2) ** 1.5)
|
32 |
return score
|
33 |
|
34 |
-
def fetch_papers(self):
|
35 |
try:
|
36 |
-
response = requests.get(f"{API_URL}?limit=
|
37 |
response.raise_for_status()
|
38 |
data = response.json()
|
39 |
|
40 |
-
|
41 |
-
self.
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
self.total_pages = max((len(self.papers) + self.papers_per_page - 1) // self.papers_per_page, 1)
|
48 |
self.current_page = 1
|
@@ -112,17 +139,12 @@ class PaperManager:
|
|
112 |
|
113 |
paper_manager = PaperManager()
|
114 |
|
115 |
-
def
|
116 |
-
if paper_manager.fetch_papers():
|
117 |
-
return paper_manager.render_papers()
|
118 |
else:
|
119 |
-
return "<div class='no-papers'>Failed to fetch papers. Please try again later.</div>"
|
120 |
|
121 |
-
def refresh_papers():
|
122 |
-
if paper_manager.fetch_papers():
|
123 |
-
return paper_manager.render_papers()
|
124 |
-
else:
|
125 |
-
return "<div class='no-papers'>Failed to refresh papers. Please try again later.</div>"
|
126 |
|
127 |
css = """
|
128 |
body {
|
@@ -131,63 +153,51 @@ body {
|
|
131 |
margin: 0;
|
132 |
padding: 0;
|
133 |
}
|
134 |
-
|
135 |
a {
|
136 |
color: #0000ff;
|
137 |
text-decoration: none;
|
138 |
}
|
139 |
-
|
140 |
a:visited {
|
141 |
color: #551A8B;
|
142 |
}
|
143 |
-
|
144 |
.container {
|
145 |
width: 85%;
|
146 |
margin: auto;
|
147 |
}
|
148 |
-
|
149 |
table {
|
150 |
width: 100%;
|
151 |
}
|
152 |
-
|
153 |
.header-table {
|
154 |
width: 100%;
|
155 |
background-color: #ff6600;
|
156 |
padding: 2px 10px;
|
157 |
}
|
158 |
-
|
159 |
.header-table a {
|
160 |
color: black;
|
161 |
font-weight: bold;
|
162 |
font-size: 14pt;
|
163 |
text-decoration: none;
|
164 |
}
|
165 |
-
|
166 |
.itemlist .athing {
|
167 |
background-color: #f6f6ef;
|
168 |
}
|
169 |
-
|
170 |
.rank {
|
171 |
font-size: 14pt;
|
172 |
color: #828282;
|
173 |
padding-right: 5px;
|
174 |
}
|
175 |
-
|
176 |
.storylink {
|
177 |
font-size: 10pt;
|
178 |
}
|
179 |
-
|
180 |
.subtext {
|
181 |
font-size: 8pt;
|
182 |
color: #828282;
|
183 |
padding-left: 40px;
|
184 |
}
|
185 |
-
|
186 |
.subtext a {
|
187 |
color: #828282;
|
188 |
text-decoration: none;
|
189 |
}
|
190 |
-
|
191 |
#refresh-button {
|
192 |
background: none;
|
193 |
border: none;
|
@@ -196,71 +206,56 @@ table {
|
|
196 |
font-size: 14pt;
|
197 |
cursor: pointer;
|
198 |
}
|
199 |
-
|
200 |
.no-papers {
|
201 |
text-align: center;
|
202 |
color: #828282;
|
203 |
padding: 1rem;
|
204 |
font-size: 14pt;
|
205 |
}
|
206 |
-
|
207 |
@media (max-width: 640px) {
|
208 |
.header-table a {
|
209 |
font-size: 12pt;
|
210 |
}
|
211 |
-
|
212 |
.storylink {
|
213 |
font-size: 9pt;
|
214 |
}
|
215 |
-
|
216 |
.subtext {
|
217 |
font-size: 7pt;
|
218 |
}
|
219 |
}
|
220 |
-
|
221 |
/* Dark mode */
|
222 |
@media (prefers-color-scheme: dark) {
|
223 |
body {
|
224 |
background-color: #121212;
|
225 |
color: #e0e0e0;
|
226 |
}
|
227 |
-
|
228 |
a {
|
229 |
color: #add8e6;
|
230 |
}
|
231 |
-
|
232 |
a:visited {
|
233 |
color: #9370db;
|
234 |
}
|
235 |
-
|
236 |
.header-table {
|
237 |
background-color: #ff6600;
|
238 |
}
|
239 |
-
|
240 |
.header-table a {
|
241 |
color: black;
|
242 |
}
|
243 |
-
|
244 |
.itemlist .athing {
|
245 |
background-color: #1e1e1e;
|
246 |
}
|
247 |
-
|
248 |
.rank {
|
249 |
color: #b0b0b0;
|
250 |
}
|
251 |
-
|
252 |
.subtext {
|
253 |
color: #b0b0b0;
|
254 |
}
|
255 |
-
|
256 |
.subtext a {
|
257 |
color: #b0b0b0;
|
258 |
}
|
259 |
-
|
260 |
#refresh-button {
|
261 |
color: #e0e0e0;
|
262 |
}
|
263 |
-
|
264 |
.no-papers {
|
265 |
color: #b0b0b0;
|
266 |
}
|
@@ -285,6 +280,7 @@ with demo:
|
|
285 |
Once your paper is submitted, it will automatically appear in this demo.
|
286 |
""")
|
287 |
# Header with Refresh Button
|
|
|
288 |
with gr.Row():
|
289 |
gr.HTML("""
|
290 |
<table border="0" cellpadding="0" cellspacing="0" class="header-table">
|
@@ -300,22 +296,44 @@ with demo:
|
|
300 |
</tr>
|
301 |
</table>
|
302 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
# Paper list
|
304 |
paper_list = gr.HTML()
|
|
|
305 |
# Navigation Buttons
|
306 |
with gr.Row():
|
307 |
prev_button = gr.Button("Prev")
|
308 |
next_button = gr.Button("Next")
|
309 |
|
310 |
-
|
311 |
# Load papers on app start
|
312 |
-
demo.load(
|
|
|
|
|
|
|
|
|
313 |
|
314 |
# Button clicks
|
315 |
prev_button.click(paper_manager.prev_page, outputs=[paper_list])
|
316 |
next_button.click(paper_manager.next_page, outputs=[paper_list])
|
317 |
refresh_button = gr.Button("Refresh", visible=False, elem_id="refresh-hidden")
|
318 |
-
refresh_button.click(
|
319 |
|
320 |
# Bind the visible Refresh button to the hidden one using JavaScript
|
321 |
gr.HTML("""
|
|
|
1 |
import gradio as gr
|
2 |
import requests
|
3 |
+
from datetime import datetime, timezone, timedelta
|
4 |
|
5 |
API_URL = "https://huggingface.co/api/daily_papers"
|
6 |
|
|
|
10 |
self.current_page = 1
|
11 |
self.papers = []
|
12 |
self.total_pages = 1
|
13 |
+
self.sort_method = "hot"
|
14 |
+
self.time_period = "day"
|
15 |
|
16 |
+
def calculate_hot_score(self, paper):
|
17 |
"""
|
18 |
Calculate the score of a paper based on upvotes and age.
|
19 |
This mimics the "hotness" algorithm used by platforms like Hacker News.
|
|
|
23 |
try:
|
24 |
published_time = datetime.fromisoformat(published_at_str.replace('Z', '+00:00'))
|
25 |
except ValueError:
|
|
|
26 |
published_time = datetime.now(timezone.utc)
|
27 |
|
28 |
time_diff = datetime.now(timezone.utc) - published_time
|
29 |
+
time_diff_hours = time_diff.total_seconds() / 3600
|
30 |
|
|
|
31 |
score = upvotes / ((time_diff_hours + 2) ** 1.5)
|
32 |
return score
|
33 |
|
34 |
+
def fetch_papers(self, sort_method, time_period=None):
|
35 |
try:
|
36 |
+
response = requests.get(f"{API_URL}?limit=1000") # Increased limit to get more papers
|
37 |
response.raise_for_status()
|
38 |
data = response.json()
|
39 |
|
40 |
+
self.sort_method = sort_method
|
41 |
+
self.time_period = time_period if sort_method == "top" else None
|
42 |
+
|
43 |
+
if sort_method == "hot":
|
44 |
+
self.papers = sorted(
|
45 |
+
data,
|
46 |
+
key=lambda x: self.calculate_hot_score(x),
|
47 |
+
reverse=True
|
48 |
+
)
|
49 |
+
elif sort_method == "top":
|
50 |
+
current_time = datetime.now(timezone.utc)
|
51 |
+
|
52 |
+
if time_period == "day":
|
53 |
+
start_time = current_time - timedelta(days=1)
|
54 |
+
elif time_period == "week":
|
55 |
+
start_time = current_time - timedelta(days=7)
|
56 |
+
elif time_period == "month":
|
57 |
+
start_time = current_time - timedelta(days=30)
|
58 |
+
elif time_period == "year":
|
59 |
+
start_time = current_time - timedelta(days=365)
|
60 |
+
else:
|
61 |
+
start_time = datetime.min.replace(tzinfo=timezone.utc)
|
62 |
+
|
63 |
+
filtered_papers = [
|
64 |
+
paper for paper in data
|
65 |
+
if datetime.fromisoformat(paper.get('publishedAt', '').replace('Z', '+00:00')) >= start_time
|
66 |
+
]
|
67 |
+
|
68 |
+
self.papers = sorted(
|
69 |
+
filtered_papers,
|
70 |
+
key=lambda x: x.get('paper', {}).get('upvotes', 0),
|
71 |
+
reverse=True
|
72 |
+
)
|
73 |
|
74 |
self.total_pages = max((len(self.papers) + self.papers_per_page - 1) // self.papers_per_page, 1)
|
75 |
self.current_page = 1
|
|
|
139 |
|
140 |
paper_manager = PaperManager()
|
141 |
|
142 |
+
def update_papers(sort_method, time_period=None):
|
143 |
+
if paper_manager.fetch_papers(sort_method, time_period):
|
144 |
+
return paper_manager.render_papers(), gr.update(visible=(sort_method == "top"))
|
145 |
else:
|
146 |
+
return "<div class='no-papers'>Failed to fetch papers. Please try again later.</div>", gr.update(visible=False)
|
147 |
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
css = """
|
150 |
body {
|
|
|
153 |
margin: 0;
|
154 |
padding: 0;
|
155 |
}
|
|
|
156 |
a {
|
157 |
color: #0000ff;
|
158 |
text-decoration: none;
|
159 |
}
|
|
|
160 |
a:visited {
|
161 |
color: #551A8B;
|
162 |
}
|
|
|
163 |
.container {
|
164 |
width: 85%;
|
165 |
margin: auto;
|
166 |
}
|
|
|
167 |
table {
|
168 |
width: 100%;
|
169 |
}
|
|
|
170 |
.header-table {
|
171 |
width: 100%;
|
172 |
background-color: #ff6600;
|
173 |
padding: 2px 10px;
|
174 |
}
|
|
|
175 |
.header-table a {
|
176 |
color: black;
|
177 |
font-weight: bold;
|
178 |
font-size: 14pt;
|
179 |
text-decoration: none;
|
180 |
}
|
|
|
181 |
.itemlist .athing {
|
182 |
background-color: #f6f6ef;
|
183 |
}
|
|
|
184 |
.rank {
|
185 |
font-size: 14pt;
|
186 |
color: #828282;
|
187 |
padding-right: 5px;
|
188 |
}
|
|
|
189 |
.storylink {
|
190 |
font-size: 10pt;
|
191 |
}
|
|
|
192 |
.subtext {
|
193 |
font-size: 8pt;
|
194 |
color: #828282;
|
195 |
padding-left: 40px;
|
196 |
}
|
|
|
197 |
.subtext a {
|
198 |
color: #828282;
|
199 |
text-decoration: none;
|
200 |
}
|
|
|
201 |
#refresh-button {
|
202 |
background: none;
|
203 |
border: none;
|
|
|
206 |
font-size: 14pt;
|
207 |
cursor: pointer;
|
208 |
}
|
|
|
209 |
.no-papers {
|
210 |
text-align: center;
|
211 |
color: #828282;
|
212 |
padding: 1rem;
|
213 |
font-size: 14pt;
|
214 |
}
|
|
|
215 |
@media (max-width: 640px) {
|
216 |
.header-table a {
|
217 |
font-size: 12pt;
|
218 |
}
|
|
|
219 |
.storylink {
|
220 |
font-size: 9pt;
|
221 |
}
|
|
|
222 |
.subtext {
|
223 |
font-size: 7pt;
|
224 |
}
|
225 |
}
|
|
|
226 |
/* Dark mode */
|
227 |
@media (prefers-color-scheme: dark) {
|
228 |
body {
|
229 |
background-color: #121212;
|
230 |
color: #e0e0e0;
|
231 |
}
|
|
|
232 |
a {
|
233 |
color: #add8e6;
|
234 |
}
|
|
|
235 |
a:visited {
|
236 |
color: #9370db;
|
237 |
}
|
|
|
238 |
.header-table {
|
239 |
background-color: #ff6600;
|
240 |
}
|
|
|
241 |
.header-table a {
|
242 |
color: black;
|
243 |
}
|
|
|
244 |
.itemlist .athing {
|
245 |
background-color: #1e1e1e;
|
246 |
}
|
|
|
247 |
.rank {
|
248 |
color: #b0b0b0;
|
249 |
}
|
|
|
250 |
.subtext {
|
251 |
color: #b0b0b0;
|
252 |
}
|
|
|
253 |
.subtext a {
|
254 |
color: #b0b0b0;
|
255 |
}
|
|
|
256 |
#refresh-button {
|
257 |
color: #e0e0e0;
|
258 |
}
|
|
|
259 |
.no-papers {
|
260 |
color: #b0b0b0;
|
261 |
}
|
|
|
280 |
Once your paper is submitted, it will automatically appear in this demo.
|
281 |
""")
|
282 |
# Header with Refresh Button
|
283 |
+
|
284 |
with gr.Row():
|
285 |
gr.HTML("""
|
286 |
<table border="0" cellpadding="0" cellspacing="0" class="header-table">
|
|
|
296 |
</tr>
|
297 |
</table>
|
298 |
""")
|
299 |
+
|
300 |
+
# Sorting method selection
|
301 |
+
with gr.Row():
|
302 |
+
sort_method = gr.Radio(
|
303 |
+
choices=["hot", "top"],
|
304 |
+
value="hot",
|
305 |
+
label="Sort by:",
|
306 |
+
interactive=True
|
307 |
+
)
|
308 |
+
|
309 |
+
# Time Period Dropdown (only visible when "top" is selected)
|
310 |
+
time_period = gr.Dropdown(
|
311 |
+
choices=["day", "week", "month", "year"],
|
312 |
+
value="day",
|
313 |
+
label="Top papers from the last:",
|
314 |
+
visible=False
|
315 |
+
)
|
316 |
+
|
317 |
# Paper list
|
318 |
paper_list = gr.HTML()
|
319 |
+
|
320 |
# Navigation Buttons
|
321 |
with gr.Row():
|
322 |
prev_button = gr.Button("Prev")
|
323 |
next_button = gr.Button("Next")
|
324 |
|
|
|
325 |
# Load papers on app start
|
326 |
+
demo.load(lambda: update_papers("hot"), outputs=[paper_list, time_period])
|
327 |
+
|
328 |
+
# Update papers when sorting method or time period changes
|
329 |
+
sort_method.change(update_papers, inputs=[sort_method, time_period], outputs=[paper_list, time_period])
|
330 |
+
time_period.change(lambda s, t: update_papers(s, t), inputs=[sort_method, time_period], outputs=[paper_list, time_period])
|
331 |
|
332 |
# Button clicks
|
333 |
prev_button.click(paper_manager.prev_page, outputs=[paper_list])
|
334 |
next_button.click(paper_manager.next_page, outputs=[paper_list])
|
335 |
refresh_button = gr.Button("Refresh", visible=False, elem_id="refresh-hidden")
|
336 |
+
refresh_button.click(update_papers, inputs=[sort_method, time_period], outputs=[paper_list, time_period])
|
337 |
|
338 |
# Bind the visible Refresh button to the hidden one using JavaScript
|
339 |
gr.HTML("""
|