openfree commited on
Commit
9338bc8
β€’
1 Parent(s): b086826

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -858
app.py CHANGED
@@ -1,863 +1,6 @@
1
- import os
2
- import random
3
- import base64
4
- import requests
5
- from selenium import webdriver
6
- from selenium.webdriver.support.ui import WebDriverWait
7
- from selenium.webdriver.support import expected_conditions as EC
8
- from selenium.webdriver.common.by import By
9
- from selenium.common.exceptions import WebDriverException, TimeoutException
10
- from PIL import Image
11
- from io import BytesIO
12
- from datetime import datetime
13
- import gradio as gr
14
- from typing import Tuple
15
- import time
16
- from pathlib import Path # μΆ”κ°€
17
-
18
- # μŠ€ν¬λ¦°μƒ· μΊμ‹œ 디렉토리 μ„€μ •
19
- CACHE_DIR = Path("screenshot_cache")
20
- CACHE_DIR.mkdir(exist_ok=True)
21
-
22
- # μ „μ—­ λ³€μˆ˜λ‘œ μŠ€ν¬λ¦°μƒ· μΊμ‹œ μ„ μ–Έ
23
- SCREENSHOT_CACHE = {}
24
-
25
- def get_cached_screenshot(url: str) -> str:
26
- """μΊμ‹œλœ μŠ€ν¬λ¦°μƒ· κ°€μ Έμ˜€κΈ° λ˜λŠ” μƒˆλ‘œ 생성"""
27
- cache_file = CACHE_DIR / f"{base64.b64encode(url.encode()).decode()}.png"
28
-
29
- if cache_file.exists():
30
- with open(cache_file, "rb") as f:
31
- return base64.b64encode(f.read()).decode()
32
-
33
- return take_screenshot(url)
34
-
35
- def take_screenshot(url):
36
- """μ›Ήμ‚¬μ΄νŠΈ μŠ€ν¬λ¦°μƒ· 촬영 ν•¨μˆ˜ (λ‘œλ”© λŒ€κΈ° μ‹œκ°„ μΆ”κ°€)"""
37
- if url in SCREENSHOT_CACHE:
38
- return SCREENSHOT_CACHE[url]
39
-
40
- if not url.startswith('http'):
41
- url = f"https://{url}"
42
-
43
- options = webdriver.ChromeOptions()
44
- options.add_argument('--headless')
45
- options.add_argument('--no-sandbox')
46
- options.add_argument('--disable-dev-shm-usage')
47
- options.add_argument('--window-size=1080,720')
48
-
49
- try:
50
- driver = webdriver.Chrome(options=options)
51
- driver.get(url)
52
-
53
- # λͺ…μ‹œμ  λŒ€κΈ°: body μš”μ†Œκ°€ λ‘œλ“œλ  λ•ŒκΉŒμ§€ λŒ€κΈ° (μ΅œλŒ€ 10초)
54
- try:
55
- WebDriverWait(driver, 10).until(
56
- EC.presence_of_element_located((By.TAG_NAME, "body"))
57
- )
58
- except TimeoutException:
59
- print(f"νŽ˜μ΄μ§€ λ‘œλ”© νƒ€μž„μ•„μ›ƒ: {url}")
60
-
61
- # μΆ”κ°€ λŒ€κΈ° μ‹œκ°„μ„ 2초둜 증가
62
- time.sleep(2) # 1μ΄ˆμ—μ„œ 2초둜 λ³€κ²½
63
-
64
- # JavaScript μ‹€ν–‰ μ™„λ£Œ λŒ€κΈ°
65
- driver.execute_script("return document.readyState") == "complete"
66
-
67
- # μŠ€ν¬λ¦°μƒ· 촬영
68
- screenshot = driver.get_screenshot_as_png()
69
- img = Image.open(BytesIO(screenshot))
70
- buffered = BytesIO()
71
- img.save(buffered, format="PNG")
72
- base64_image = base64.b64encode(buffered.getvalue()).decode()
73
-
74
- # μΊμ‹œμ— μ €μž₯
75
- SCREENSHOT_CACHE[url] = base64_image
76
- return base64_image
77
-
78
- except WebDriverException as e:
79
- print(f"μŠ€ν¬λ¦°μƒ· 촬영 μ‹€νŒ¨: {str(e)} for URL: {url}")
80
- return None
81
- except Exception as e:
82
- print(f"μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {str(e)} for URL: {url}")
83
- return None
84
- finally:
85
- if 'driver' in locals():
86
- driver.quit()
87
-
88
- from datetime import datetime, timedelta
89
-
90
- def calculate_rising_rate(created_date: str, rank: int) -> int:
91
- """AI Rising Rate 계산"""
92
- # 생성일 κΈ°μ€€ 점수 계산
93
- created = datetime.strptime(created_date.split('T')[0], '%Y-%m-%d')
94
- today = datetime.now()
95
- days_diff = (today - created).days
96
- date_score = max(0, 300 - days_diff) # μ΅œλŒ€ 300점
97
-
98
- # μˆœμœ„ κΈ°μ€€ 점수 계산
99
- rank_score = max(0, 300 - rank) # μ΅œλŒ€ 300점
100
-
101
- # 총점 계산
102
- total_score = date_score + rank_score
103
-
104
- # 별 개수 계산 (0~5)
105
- if total_score <= 100:
106
- stars = 1
107
- elif total_score <= 200:
108
- stars = 2
109
- elif total_score <= 300:
110
- stars = 3
111
- elif total_score <= 400:
112
- stars = 4
113
- else:
114
- stars = 5
115
-
116
- return stars
117
-
118
- def get_popularity_grade(likes: int, stars: int) -> tuple:
119
- """AI Popularity Score λ“±κΈ‰ 계산"""
120
- # 기본 점수 (likes)
121
- base_score = min(likes, 10000) # μ΅œλŒ€ 10000점
122
-
123
- # 별점 μΆ”κ°€ 점수 (별 ν•˜λ‚˜λ‹Ή 500점)
124
- star_score = stars * 500
125
-
126
- # 총점
127
- total_score = base_score + star_score
128
-
129
- # λ“±κΈ‰ ν…Œμ΄λΈ” (18단계)
130
- grades = [
131
- (9000, "AAA+"), (8500, "AAA"), (8000, "AAA-"),
132
- (7500, "AA+"), (7000, "AA"), (6500, "AA-"),
133
- (6000, "A+"), (5500, "A"), (5000, "A-"),
134
- (4500, "BBB+"), (4000, "BBB"), (3500, "BBB-"),
135
- (3000, "BB+"), (2500, "BB"), (2000, "BB-"),
136
- (1500, "B+"), (1000, "B"), (500, "B-")
137
- ]
138
-
139
- for threshold, grade in grades:
140
- if total_score >= threshold:
141
- return grade, total_score
142
-
143
- return "B-", total_score
144
-
145
- # get_card ν•¨μˆ˜ λ‚΄μ˜ hardware_info 뢀뢄을 λ‹€μŒμœΌλ‘œ ꡐ체:
146
- def get_rating_info(item: dict, index: int) -> str:
147
- """평가 정보 HTML 생성"""
148
- created = item.get('createdAt', '').split('T')[0]
149
- likes = int(str(item.get('likes', '0')).replace(',', ''))
150
-
151
- # AI Rising Rate 계산
152
- stars = calculate_rising_rate(created, index + 1)
153
- star_html = "β˜…" * stars + "β˜†" * (5 - stars) # μ±„μ›Œμ§„ 별과 빈 별 μ‘°ν•©
154
-
155
- # AI Popularity Score 계산
156
- grade, score = get_popularity_grade(likes, stars)
157
-
158
- # 등급별 색상 μ„€μ •
159
- grade_colors = {
160
- 'AAA': '#FFD700', 'AA': '#FFA500', 'A': '#FF4500',
161
- 'BBB': '#4169E1', 'BB': '#1E90FF', 'B': '#00BFFF'
162
- }
163
- grade_base = grade.rstrip('+-')
164
- grade_color = grade_colors.get(grade_base, '#666666')
165
-
166
- return f"""
167
- <div style='
168
- margin-top: 15px;
169
- padding: 15px;
170
- background: rgba(255,255,255,0.4);
171
- border-radius: 10px;
172
- font-size: 0.9em;
173
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);'>
174
- <div style='
175
- display: grid;
176
- grid-template-columns: repeat(2, 1fr);
177
- gap: 15px;'>
178
- <div style='
179
- color: #333;
180
- display: flex;
181
- flex-direction: column;
182
- gap: 5px;'>
183
- <span style='font-weight: bold;'>AI Rising Rate:</span>
184
- <span style='
185
- color: #FF8C00;
186
- font-size: 1.4em;
187
- letter-spacing: 2px;
188
- text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{star_html}</span>
189
- </div>
190
- <div style='
191
- color: #333;
192
- display: flex;
193
- flex-direction: column;
194
- gap: 5px;'>
195
- <span style='font-weight: bold;'>AI Popularity Score:</span>
196
- <span style='
197
- font-size: 1.2em;
198
- font-weight: bold;
199
- color: {grade_color};
200
- text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{grade} ({score:,})</span>
201
- </div>
202
- </div>
203
- </div>
204
- """
205
-
206
- def get_hardware_info(item: dict) -> tuple:
207
- """ν•˜λ“œμ›¨μ–΄ 정보 μΆ”μΆœ"""
208
- try:
209
- # runtime 정보 확인
210
- runtime = item.get('runtime', {})
211
-
212
- # CPU 정보 처리
213
- cpu_info = runtime.get('cpu', 'Standard')
214
-
215
- # GPU 정보 처리
216
- gpu_info = "None"
217
- if runtime.get('accelerator') == "gpu":
218
- gpu_type = runtime.get('gpu', {}).get('name', '')
219
- gpu_memory = runtime.get('gpu', {}).get('memory', '')
220
- if gpu_type:
221
- gpu_info = f"{gpu_type}"
222
- if gpu_memory:
223
- gpu_info += f" ({gpu_memory}GB)"
224
-
225
- # spaces decorator 확인
226
- if '@spaces.GPU' in str(item.get('sdk_version', '')):
227
- if gpu_info == "None":
228
- gpu_info = "GPU Enabled"
229
-
230
- # SDK 정보 처리
231
- sdk = item.get('sdk', 'N/A')
232
-
233
- print(f"Debug - Runtime Info: {runtime}") # 디버그 좜λ ₯
234
- print(f"Debug - GPU Info: {gpu_info}") # 디버그 좜λ ₯
235
-
236
- return cpu_info, gpu_info, sdk
237
-
238
- except Exception as e:
239
- print(f"Error parsing hardware info: {str(e)}")
240
- return 'Standard', 'None', 'N/A'
241
-
242
- def get_card(item: dict, index: int, card_type: str = "space") -> str:
243
- """톡합 μΉ΄λ“œ HTML 생성"""
244
- item_id = item.get('id', '')
245
- author, title = item_id.split('/', 1)
246
- likes = format(item.get('likes', 0), ',')
247
- created = item.get('createdAt', '').split('T')[0]
248
-
249
- # short_description κ°€μ Έμ˜€κΈ°
250
- short_description = item.get('cardData', {}).get('short_description', '')
251
-
252
- # titleκ³Ό short_description을 ν¬ν•¨ν•œ 헀더 HTML
253
- title_html = f"""
254
- <h3 style='
255
- margin: 0 0 15px 0;
256
- color: #333;
257
- font-size: 1.3em;
258
- line-height: 1.4;
259
- display: -webkit-box;
260
- -webkit-line-clamp: 2;
261
- -webkit-box-orient: vertical;
262
- overflow: hidden;
263
- text-overflow: ellipsis;
264
- text-shadow: 1px 1px 1px rgba(255,255,255,0.8);'>
265
- {title}
266
- {f'<span style="display: block; font-size: 0.7em; color: #666; margin-top: 5px; font-weight: normal; font-style: italic;">{short_description}</span>' if short_description else ''}
267
- </h3>
268
- """
269
-
270
-
271
- # URL μ •μ˜
272
- if card_type == "space":
273
- url = f"https://huggingface.co/spaces/{item_id}"
274
- elif card_type == "model":
275
- url = f"https://huggingface.co/{item_id}"
276
- else: # dataset
277
- url = f"https://huggingface.co/datasets/{item_id}"
278
-
279
- # 메타데이터 처리
280
- tags = item.get('tags', [])
281
- pipeline_tag = item.get('pipeline_tag', '')
282
- license = item.get('license', '')
283
- sdk = item.get('sdk', 'N/A')
284
-
285
- # AI Rating 정보 κ°€μ Έμ˜€κΈ°
286
- rating_info = get_rating_info(item, index)
287
-
288
- # μΉ΄λ“œ νƒ€μž…λ³„ κ·ΈλΌλ°μ΄μ…˜ μ„€μ •
289
- if card_type == "space":
290
- gradient_colors = """
291
- rgba(255, 182, 193, 0.7), /* νŒŒμŠ€ν…” 핑크 */
292
- rgba(173, 216, 230, 0.7), /* νŒŒμŠ€ν…” 블루 */
293
- rgba(255, 218, 185, 0.7) /* νŒŒμŠ€ν…” ν”ΌμΉ˜ */
294
- """
295
- bg_content = f"""
296
- background-image: url(data:image/png;base64,{get_cached_screenshot(url) if get_cached_screenshot(url) else ''});
297
- background-size: cover;
298
- background-position: center;
299
- """
300
- type_icon = "🎯"
301
- type_label = "SPACE"
302
- elif card_type == "model":
303
- gradient_colors = """
304
- rgba(110, 142, 251, 0.7), /* λͺ¨λΈ 블루 */
305
- rgba(130, 158, 251, 0.7),
306
- rgba(150, 174, 251, 0.7)
307
- """
308
- bg_content = f"""
309
- background: linear-gradient(135deg, #6e8efb, #4a6cf7);
310
- padding: 15px;
311
- """
312
- type_icon = "πŸ€–"
313
- type_label = "MODEL"
314
- else: # dataset
315
- gradient_colors = """
316
- rgba(255, 107, 107, 0.7), /* 데이터셋 λ ˆλ“œ */
317
- rgba(255, 127, 127, 0.7),
318
- rgba(255, 147, 147, 0.7)
319
- """
320
- bg_content = f"""
321
- background: linear-gradient(135deg, #ff6b6b, #ff8787);
322
- padding: 15px;
323
- """
324
- type_icon = "πŸ“Š"
325
- type_label = "DATASET"
326
-
327
- content_bg = f"""
328
- background: linear-gradient(135deg, {gradient_colors});
329
- backdrop-filter: blur(10px);
330
- """
331
-
332
-
333
- # νƒœκ·Έ ν‘œμ‹œ (models와 datasets용)
334
- tags_html = ""
335
- if card_type != "space":
336
- tags_html = f"""
337
- <div style='
338
- position: absolute;
339
- top: 50%;
340
- left: 50%;
341
- transform: translate(-50%, -50%);
342
- display: flex;
343
- flex-wrap: wrap;
344
- gap: 5px;
345
- justify-content: center;
346
- width: 90%;'>
347
- {' '.join([f'''
348
- <span style='
349
- background: rgba(255,255,255,0.2);
350
- padding: 5px 10px;
351
- border-radius: 15px;
352
- color: white;
353
- font-size: 0.8em;'>
354
- #{tag}
355
- </span>
356
- ''' for tag in tags[:5]])}
357
- </div>
358
- """
359
-
360
- # μΉ΄λ“œ HTML λ°˜ν™˜
361
- return f"""
362
- <div class="card" style='
363
- position: relative;
364
- border: none;
365
- padding: 0;
366
- margin: 10px;
367
- border-radius: 20px;
368
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
369
- background: white;
370
- transition: all 0.3s ease;
371
- overflow: hidden;
372
- min-height: 400px;
373
- cursor: pointer;
374
- transform-origin: center;'
375
- onmouseover="this.style.transform='scale(0.98) translateY(5px)'; this.style.boxShadow='0 5px 15px rgba(0,0,0,0.2)';"
376
- onmouseout="this.style.transform='scale(1) translateY(0)'; this.style.boxShadow='0 10px 20px rgba(0,0,0,0.1)';"
377
- onclick="window.open('{url}', '_blank')">
378
-
379
- <!-- 상단 μ˜μ—­ -->
380
- <div style='
381
- width: 100%;
382
- height: 200px;
383
- {bg_content}
384
- position: relative;'>
385
-
386
- <!-- μˆœμœ„ 뱃지 -->
387
- <div style='
388
- position: absolute;
389
- top: 10px;
390
- left: 10px;
391
- background: rgba(0,0,0,0.7);
392
- color: white;
393
- padding: 5px 15px;
394
- border-radius: 20px;
395
- font-weight: bold;
396
- font-size: 0.9em;
397
- backdrop-filter: blur(5px);'>
398
- #{index + 1}
399
- </div>
400
-
401
- <!-- νƒ€μž… 뱃지 -->
402
- <div style='
403
- position: absolute;
404
- top: 10px;
405
- right: 10px;
406
- background: rgba(255,255,255,0.9);
407
- padding: 5px 15px;
408
- border-radius: 20px;
409
- font-weight: bold;
410
- font-size: 0.8em;'>
411
- {type_icon} {type_label}
412
- </div>
413
-
414
- {tags_html}
415
- </div>
416
-
417
- <!-- μ½˜ν…μΈ  μ˜μ—­ -->
418
- <div style='
419
- padding: 20px;
420
- {content_bg}
421
- border-radius: 0 0 20px 20px;
422
- border-top: 1px solid rgba(255,255,255,0.5);'>
423
- <h3 style='
424
- margin: 0 0 15px 0;
425
- color: #333;
426
- font-size: 1.3em;
427
- line-height: 1.4;
428
- display: -webkit-box;
429
- -webkit-line-clamp: 2;
430
- -webkit-box-orient: vertical;
431
- overflow: hidden;
432
- text-overflow: ellipsis;
433
- text-shadow: 1px 1px 1px rgba(255,255,255,0.8);'>
434
- {title}
435
- </h3>
436
-
437
- <div style='
438
- display: grid;
439
- grid-template-columns: repeat(2, 1fr);
440
- gap: 10px;
441
- font-size: 0.9em;
442
- background: rgba(255,255,255,0.3);
443
- padding: 10px;
444
- border-radius: 10px;'>
445
- <div style='color: #444;'>
446
- <span style='margin-right: 5px;'>πŸ‘€</span> {author}
447
- </div>
448
- <div style='color: #444;'>
449
- <span style='margin-right: 5px;'>❀️</span> {likes}
450
- </div>
451
- <div style='color: #444; grid-column: span 2;'>
452
- <span style='margin-right: 5px;'>πŸ“…</span> {created}
453
- </div>
454
- </div>
455
-
456
- {rating_info}
457
- </div>
458
- </div>
459
- """
460
-
461
- def get_trending_spaces(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
462
- """νŠΈλ Œλ”© 슀페이슀 κ°€μ Έμ˜€κΈ°"""
463
- url = "https://huggingface.co/api/spaces"
464
-
465
- try:
466
- progress(0, desc="Fetching spaces data...")
467
- params = {
468
- 'full': 'true',
469
- 'limit': 10
470
- }
471
-
472
- response = requests.get(url, params=params)
473
- response.raise_for_status()
474
- spaces = response.json()
475
-
476
- # κ²€μƒ‰μ–΄λ‘œ 필터링
477
- if search_query:
478
- spaces = [space for space in spaces if search_query.lower() in
479
- (space.get('id', '') + ' ' + space.get('title', '')).lower()]
480
-
481
- # μ •λ ¬
482
- sort_by = sort_by.lower()
483
- if sort_by == "rising_rate":
484
- spaces.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
485
- elif sort_by == "popularity":
486
- spaces.sort(key=lambda x: get_popularity_grade(
487
- int(str(x.get('likes', '0')).replace(',', '')),
488
- calculate_rising_rate(x.get('createdAt', ''), 0))[1],
489
- reverse=True)
490
-
491
- progress(0.1, desc="Creating gallery...")
492
- html_content = """
493
- <div style='padding: 20px; background: #f5f5f5;'>
494
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
495
- """
496
-
497
- for idx, space in enumerate(spaces):
498
- html_content += get_card(space, idx, "space")
499
- progress((0.1 + 0.9 * idx/len(spaces)), desc=f"Loading space {idx+1}/{len(spaces)}...")
500
-
501
- html_content += "</div></div>"
502
-
503
- progress(1.0, desc="Complete!")
504
- return html_content, f"Found {len(spaces)} spaces"
505
-
506
- except Exception as e:
507
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
508
- return error_html, f"Error: {str(e)}"
509
-
510
- def get_models(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
511
- """인기 λͺ¨λΈ κ°€μ Έμ˜€κΈ°"""
512
- url = "https://huggingface.co/api/models"
513
-
514
- try:
515
- progress(0, desc="Fetching models data...")
516
- params = {
517
- 'full': 'true',
518
- 'limit': 300
519
- }
520
- response = requests.get(url, params=params)
521
- response.raise_for_status()
522
- models = response.json()
523
-
524
- # κ²€μƒ‰μ–΄λ‘œ 필터링
525
- if search_query:
526
- models = [model for model in models if search_query.lower() in
527
- (model.get('id', '') + ' ' + model.get('title', '')).lower()]
528
-
529
- # μ •λ ¬
530
- sort_by = sort_by.lower()
531
- if sort_by == "rising_rate":
532
- models.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
533
- elif sort_by == "popularity":
534
- models.sort(key=lambda x: get_popularity_grade(
535
- int(str(x.get('likes', '0')).replace(',', '')),
536
- calculate_rising_rate(x.get('createdAt', ''), 0))[1],
537
- reverse=True)
538
-
539
- progress(0.1, desc="Creating gallery...")
540
- html_content = """
541
- <div style='padding: 20px; background: #f5f5f5;'>
542
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
543
- """
544
-
545
- for idx, model in enumerate(models):
546
- html_content += get_card(model, idx, "model")
547
- progress((0.1 + 0.9 * idx/len(models)), desc=f"Loading model {idx+1}/{len(models)}...")
548
-
549
- html_content += "</div></div>"
550
-
551
- progress(1.0, desc="Complete!")
552
- return html_content, f"Found {len(models)} models"
553
-
554
- except Exception as e:
555
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
556
- return error_html, f"Error: {str(e)}"
557
-
558
- def get_datasets(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]:
559
- """인기 데이터셋 κ°€μ Έμ˜€κΈ°"""
560
- url = "https://huggingface.co/api/datasets"
561
-
562
- try:
563
- progress(0, desc="Fetching datasets data...")
564
- params = {
565
- 'full': 'true',
566
- 'limit': 300
567
- }
568
- response = requests.get(url, params=params)
569
- response.raise_for_status()
570
- datasets = response.json()
571
-
572
- # κ²€μƒ‰μ–΄λ‘œ 필터링
573
- if search_query:
574
- datasets = [dataset for dataset in datasets if search_query.lower() in
575
- (dataset.get('id', '') + ' ' + dataset.get('title', '')).lower()]
576
-
577
- # μ •λ ¬
578
- sort_by = sort_by.lower()
579
- if sort_by == "rising_rate":
580
- datasets.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
581
- elif sort_by == "popularity":
582
- datasets.sort(key=lambda x: get_popularity_grade(
583
- int(str(x.get('likes', '0')).replace(',', '')),
584
- calculate_rising_rate(x.get('createdAt', ''), 0))[1],
585
- reverse=True)
586
-
587
- progress(0.1, desc="Creating gallery...")
588
- html_content = """
589
- <div style='padding: 20px; background: #f5f5f5;'>
590
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
591
- """
592
-
593
- for idx, dataset in enumerate(datasets):
594
- html_content += get_card(dataset, idx, "dataset")
595
- progress((0.1 + 0.9 * idx/len(datasets)), desc=f"Loading dataset {idx+1}/{len(datasets)}...")
596
-
597
- html_content += "</div></div>"
598
-
599
- progress(1.0, desc="Complete!")
600
- return html_content, f"Found {len(datasets)} datasets"
601
-
602
- except Exception as e:
603
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
604
- return error_html, f"Error: {str(e)}"
605
-
606
- # μ •λ ¬ ν•¨μˆ˜ μΆ”κ°€
607
- def sort_items(items, sort_by):
608
- if sort_by == "rank":
609
- return items # 이미 μˆœμœ„λŒ€λ‘œ μ •λ ¬λ˜μ–΄ 있음
610
- elif sort_by == "rising_rate":
611
- return sorted(items, key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True)
612
- elif sort_by == "popularity":
613
- return sorted(items, key=lambda x: get_popularity_grade(int(str(x.get('likes', '0')).replace(',', '')),
614
- calculate_rising_rate(x.get('createdAt', ''), 0))[1], reverse=True)
615
- return items
616
-
617
- # API 호좜 ν•¨μˆ˜ μˆ˜μ •
618
- def fetch_items(item_type, search_query="", sort_by="rank", limit=1000):
619
- """μ•„μ΄ν…œ κ°€μ Έμ˜€κΈ° (spaces/models/datasets)"""
620
- base_url = f"https://huggingface.co/api/{item_type}"
621
- params = {
622
- 'full': 'true',
623
- 'limit': limit,
624
- 'search': search_query
625
- }
626
-
627
- try:
628
- response = requests.get(base_url, params=params)
629
- response.raise_for_status()
630
- items = response.json()
631
-
632
- # κ²€μƒ‰μ–΄λ‘œ 필터링
633
- if search_query:
634
- items = [item for item in items if search_query.lower() in
635
- (item.get('id', '') + item.get('title', '')).lower()]
636
-
637
- # μ •λ ¬
638
- items = sort_items(items, sort_by)
639
-
640
- return items[:300] # μƒμœ„ 300개만 λ°˜ν™˜
641
- except Exception as e:
642
- print(f"Error fetching items: {e}")
643
- return []
644
-
645
- def create_interface():
646
- with gr.Blocks(title="HuggingFace Trending Board", css="""
647
- .search-sort-container {
648
- background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(240,240,255,0.95));
649
- border-radius: 15px;
650
- padding: 20px;
651
- margin: 10px 0;
652
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
653
- overflow: visible;
654
- }
655
- .search-box {
656
- border: 2px solid #e1e1e1;
657
- border-radius: 10px;
658
- padding: 12px;
659
- transition: all 0.3s ease;
660
- background: linear-gradient(135deg, #ffffff, #f8f9ff);
661
- width: 100%;
662
- }
663
- .search-box:focus {
664
- border-color: #7b61ff;
665
- box-shadow: 0 0 0 2px rgba(123,97,255,0.2);
666
- background: linear-gradient(135deg, #ffffff, #f0f3ff);
667
- }
668
- .refresh-btn {
669
- background: linear-gradient(135deg, #7b61ff, #6366f1);
670
- color: white;
671
- border: none;
672
- padding: 10px 20px;
673
- border-radius: 10px;
674
- cursor: pointer;
675
- transition: all 0.3s ease;
676
- width: 120px;
677
- height: 80px !important;
678
- display: flex;
679
- align-items: center;
680
- justify-content: center;
681
- margin-left: auto;
682
- font-size: 1.2em !important;
683
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
684
- }
685
- .refresh-btn:hover {
686
- transform: translateY(-2px);
687
- box-shadow: 0 6px 12px rgba(0,0,0,0.2);
688
- background: linear-gradient(135deg, #8b71ff, #7376f1);
689
- }
690
- """) as interface:
691
-
692
- gr.Markdown("""
693
- # πŸ€— HuggingFace Trending TOP 300 Board
694
- <div style='margin-bottom: 20px; padding: 10px; background: linear-gradient(135deg, rgba(123,97,255,0.1), rgba(99,102,241,0.1)); border-radius: 10px;'>
695
- Explore, search, and sort through the Shows Top 300 Trending spaces with AI Ratings
696
- </div>
697
- """)
698
-
699
- with gr.Tabs() as tabs:
700
- # Spaces νƒ­
701
- with gr.Tab("🎯 Trending Spaces"):
702
- with gr.Row(elem_classes="search-sort-container"):
703
- with gr.Column(scale=2):
704
- spaces_search = gr.Textbox(
705
- label="πŸ” Search Spaces",
706
- placeholder="Enter keywords to search...",
707
- elem_classes="search-box"
708
- )
709
- with gr.Column(scale=2):
710
- spaces_sort = gr.Radio(
711
- choices=["rank", "rising_rate", "popularity"],
712
- value="rank",
713
- label="Sort by",
714
- interactive=True
715
- )
716
- with gr.Column(scale=1):
717
- spaces_refresh_btn = gr.Button(
718
- "πŸ”„ Refresh",
719
- variant="primary",
720
- elem_classes="refresh-btn"
721
- )
722
- spaces_gallery = gr.HTML()
723
- spaces_status = gr.Markdown("Loading...")
724
-
725
- # Models νƒ­
726
- with gr.Tab("πŸ€– Trending Models"):
727
- with gr.Row(elem_classes="search-sort-container"):
728
- with gr.Column(scale=2):
729
- models_search = gr.Textbox(
730
- label="πŸ” Search Models",
731
- placeholder="Enter keywords to search...",
732
- elem_classes="search-box"
733
- )
734
- with gr.Column(scale=2):
735
- models_sort = gr.Radio(
736
- choices=["rank", "rising_rate", "popularity"],
737
- value="rank",
738
- label="Sort by",
739
- interactive=True
740
- )
741
- with gr.Column(scale=1):
742
- models_refresh_btn = gr.Button(
743
- "πŸ”„ Refresh",
744
- variant="primary",
745
- elem_classes="refresh-btn"
746
- )
747
- models_gallery = gr.HTML()
748
- models_status = gr.Markdown("Loading...")
749
-
750
- # Datasets νƒ­
751
- with gr.Tab("πŸ“Š Trending Datasets"):
752
- with gr.Row(elem_classes="search-sort-container"):
753
- with gr.Column(scale=2):
754
- datasets_search = gr.Textbox(
755
- label="πŸ” Search Datasets",
756
- placeholder="Enter keywords to search...",
757
- elem_classes="search-box"
758
- )
759
- with gr.Column(scale=2):
760
- datasets_sort = gr.Radio(
761
- choices=["rank", "rising_rate", "popularity"],
762
- value="rank",
763
- label="Sort by",
764
- interactive=True
765
- )
766
- with gr.Column(scale=1):
767
- datasets_refresh_btn = gr.Button(
768
- "πŸ”„ Refresh",
769
- variant="primary",
770
- elem_classes="refresh-btn"
771
- )
772
- datasets_gallery = gr.HTML()
773
- datasets_status = gr.Markdown("Loading...")
774
 
775
- # Event handlers
776
- spaces_refresh_btn.click(
777
- fn=get_trending_spaces,
778
- inputs=[spaces_search, spaces_sort],
779
- outputs=[spaces_gallery, spaces_status]
780
- )
781
-
782
- models_refresh_btn.click(
783
- fn=get_models,
784
- inputs=[models_search, models_sort],
785
- outputs=[models_gallery, models_status]
786
- )
787
-
788
- datasets_refresh_btn.click(
789
- fn=get_datasets,
790
- inputs=[datasets_search, datasets_sort],
791
- outputs=[datasets_gallery, datasets_status]
792
- )
793
-
794
- # 검색어 λ³€κ²½ μ‹œ μžλ™ μƒˆλ‘œκ³ μΉ¨
795
- spaces_search.change(
796
- fn=get_trending_spaces,
797
- inputs=[spaces_search, spaces_sort],
798
- outputs=[spaces_gallery, spaces_status]
799
- )
800
-
801
- models_search.change(
802
- fn=get_models,
803
- inputs=[models_search, models_sort],
804
- outputs=[models_gallery, models_status]
805
- )
806
-
807
- datasets_search.change(
808
- fn=get_datasets,
809
- inputs=[datasets_search, datasets_sort],
810
- outputs=[datasets_gallery, datasets_status]
811
- )
812
 
813
- # μ •λ ¬ 방식 λ³€κ²½ μ‹œ μžλ™ μƒˆλ‘œκ³ μΉ¨
814
- spaces_sort.change(
815
- fn=get_trending_spaces,
816
- inputs=[spaces_search, spaces_sort],
817
- outputs=[spaces_gallery, spaces_status]
818
- )
819
-
820
- models_sort.change(
821
- fn=get_models,
822
- inputs=[models_search, models_sort],
823
- outputs=[models_gallery, models_status]
824
- )
825
-
826
- datasets_sort.change(
827
- fn=get_datasets,
828
- inputs=[datasets_search, datasets_sort],
829
- outputs=[datasets_gallery, datasets_status]
830
- )
831
-
832
- # 초기 데이터 λ‘œλ“œ
833
- interface.load(
834
- fn=get_trending_spaces,
835
- inputs=[spaces_search, spaces_sort],
836
- outputs=[spaces_gallery, spaces_status]
837
- )
838
- interface.load(
839
- fn=get_models,
840
- inputs=[models_search, models_sort],
841
- outputs=[models_gallery, models_status]
842
- )
843
- interface.load(
844
- fn=get_datasets,
845
- inputs=[datasets_search, datasets_sort],
846
- outputs=[datasets_gallery, datasets_status]
847
- )
848
-
849
- return interface
850
-
851
- if __name__ == "__main__":
852
- try:
853
- demo = create_interface()
854
- demo.launch(
855
- share=True,
856
- inbrowser=True,
857
- show_api=False
858
- )
859
- except Exception as e:
860
- print(f"Error launching app: {e}")import os
861
  import random
862
  import base64
863
  import requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import random
5
  import base64
6
  import requests