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

Create app-backup0.py

Browse files
Files changed (1) hide show
  1. app-backup0.py +861 -0
app-backup0.py ADDED
@@ -0,0 +1,861 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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}")
861
+