openfree commited on
Commit
8fe0943
·
verified ·
1 Parent(s): b84d72f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -612
app.py CHANGED
@@ -1,613 +1,2 @@
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
- # URL 정의
250
- if card_type == "space":
251
- url = f"https://huggingface.co/spaces/{item_id}"
252
- elif card_type == "model":
253
- url = f"https://huggingface.co/{item_id}"
254
- else: # dataset
255
- url = f"https://huggingface.co/datasets/{item_id}"
256
-
257
- # 메타데이터 처리
258
- tags = item.get('tags', [])
259
- pipeline_tag = item.get('pipeline_tag', '')
260
- license = item.get('license', '')
261
- sdk = item.get('sdk', 'N/A')
262
-
263
- # AI Rating 정보 가져오기
264
- rating_info = get_rating_info(item, index)
265
-
266
- # 카드 타입별 그라데이션 설정
267
- if card_type == "space":
268
- gradient_colors = """
269
- rgba(255, 182, 193, 0.7), /* 파스텔 핑크 */
270
- rgba(173, 216, 230, 0.7), /* 파스텔 블루 */
271
- rgba(255, 218, 185, 0.7) /* 파스텔 피치 */
272
- """
273
- bg_content = f"""
274
- background-image: url(data:image/png;base64,{get_cached_screenshot(url) if get_cached_screenshot(url) else ''});
275
- background-size: cover;
276
- background-position: center;
277
- """
278
- type_icon = "🎯"
279
- type_label = "SPACE"
280
- elif card_type == "model":
281
- gradient_colors = """
282
- rgba(110, 142, 251, 0.7), /* 모델 블루 */
283
- rgba(130, 158, 251, 0.7),
284
- rgba(150, 174, 251, 0.7)
285
- """
286
- bg_content = f"""
287
- background: linear-gradient(135deg, #6e8efb, #4a6cf7);
288
- padding: 15px;
289
- """
290
- type_icon = "🤖"
291
- type_label = "MODEL"
292
- else: # dataset
293
- gradient_colors = """
294
- rgba(255, 107, 107, 0.7), /* 데이터셋 레드 */
295
- rgba(255, 127, 127, 0.7),
296
- rgba(255, 147, 147, 0.7)
297
- """
298
- bg_content = f"""
299
- background: linear-gradient(135deg, #ff6b6b, #ff8787);
300
- padding: 15px;
301
- """
302
- type_icon = "📊"
303
- type_label = "DATASET"
304
-
305
- content_bg = f"""
306
- background: linear-gradient(135deg, {gradient_colors});
307
- backdrop-filter: blur(10px);
308
- """
309
-
310
-
311
- # 태그 표시 (models와 datasets용)
312
- tags_html = ""
313
- if card_type != "space":
314
- tags_html = f"""
315
- <div style='
316
- position: absolute;
317
- top: 50%;
318
- left: 50%;
319
- transform: translate(-50%, -50%);
320
- display: flex;
321
- flex-wrap: wrap;
322
- gap: 5px;
323
- justify-content: center;
324
- width: 90%;'>
325
- {' '.join([f'''
326
- <span style='
327
- background: rgba(255,255,255,0.2);
328
- padding: 5px 10px;
329
- border-radius: 15px;
330
- color: white;
331
- font-size: 0.8em;'>
332
- #{tag}
333
- </span>
334
- ''' for tag in tags[:5]])}
335
- </div>
336
- """
337
-
338
- # 카드 HTML 반환
339
- return f"""
340
- <div class="card" style='
341
- position: relative;
342
- border: none;
343
- padding: 0;
344
- margin: 10px;
345
- border-radius: 20px;
346
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
347
- background: white;
348
- transition: all 0.3s ease;
349
- overflow: hidden;
350
- min-height: 400px;
351
- cursor: pointer;
352
- transform-origin: center;'
353
- onmouseover="this.style.transform='scale(0.98) translateY(5px)'; this.style.boxShadow='0 5px 15px rgba(0,0,0,0.2)';"
354
- onmouseout="this.style.transform='scale(1) translateY(0)'; this.style.boxShadow='0 10px 20px rgba(0,0,0,0.1)';"
355
- onclick="window.open('{url}', '_blank')">
356
-
357
- <!-- 상단 영역 -->
358
- <div style='
359
- width: 100%;
360
- height: 200px;
361
- {bg_content}
362
- position: relative;'>
363
-
364
- <!-- 순위 뱃지 -->
365
- <div style='
366
- position: absolute;
367
- top: 10px;
368
- left: 10px;
369
- background: rgba(0,0,0,0.7);
370
- color: white;
371
- padding: 5px 15px;
372
- border-radius: 20px;
373
- font-weight: bold;
374
- font-size: 0.9em;
375
- backdrop-filter: blur(5px);'>
376
- #{index + 1}
377
- </div>
378
-
379
- <!-- 타입 뱃지 -->
380
- <div style='
381
- position: absolute;
382
- top: 10px;
383
- right: 10px;
384
- background: rgba(255,255,255,0.9);
385
- padding: 5px 15px;
386
- border-radius: 20px;
387
- font-weight: bold;
388
- font-size: 0.8em;'>
389
- {type_icon} {type_label}
390
- </div>
391
-
392
- {tags_html}
393
- </div>
394
-
395
- <!-- 콘텐츠 영역 -->
396
- <div style='
397
- padding: 20px;
398
- {content_bg}
399
- border-radius: 0 0 20px 20px;
400
- border-top: 1px solid rgba(255,255,255,0.5);'>
401
- <h3 style='
402
- margin: 0 0 15px 0;
403
- color: #333;
404
- font-size: 1.3em;
405
- line-height: 1.4;
406
- display: -webkit-box;
407
- -webkit-line-clamp: 2;
408
- -webkit-box-orient: vertical;
409
- overflow: hidden;
410
- text-overflow: ellipsis;
411
- text-shadow: 1px 1px 1px rgba(255,255,255,0.8);'>
412
- {title}
413
- </h3>
414
-
415
- <div style='
416
- display: grid;
417
- grid-template-columns: repeat(2, 1fr);
418
- gap: 10px;
419
- font-size: 0.9em;
420
- background: rgba(255,255,255,0.3);
421
- padding: 10px;
422
- border-radius: 10px;'>
423
- <div style='color: #444;'>
424
- <span style='margin-right: 5px;'>👤</span> {author}
425
- </div>
426
- <div style='color: #444;'>
427
- <span style='margin-right: 5px;'>❤️</span> {likes}
428
- </div>
429
- <div style='color: #444; grid-column: span 2;'>
430
- <span style='margin-right: 5px;'>📅</span> {created}
431
- </div>
432
- </div>
433
-
434
- {rating_info}
435
- </div>
436
- </div>
437
- """
438
-
439
- def get_trending_spaces(progress=gr.Progress()) -> Tuple[str, str]:
440
- """트렌딩 스페이스 가져오기"""
441
- url = "https://huggingface.co/api/spaces"
442
-
443
- try:
444
- progress(0, desc="Fetching spaces data...")
445
- params = {
446
- 'full': 'true',
447
- 'limit': 300 # sort 파라미터 제거
448
- }
449
-
450
- response = requests.get(url, params=params)
451
- response.raise_for_status()
452
- spaces = response.json()
453
-
454
- progress(0.1, desc="Creating gallery...")
455
- html_content = """
456
- <div style='padding: 20px; background: #f5f5f5;'>
457
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
458
- """
459
-
460
- for idx, space in enumerate(spaces):
461
- html_content += get_card(space, idx, "space")
462
- progress((0.1 + 0.9 * idx/10), desc=f"Loading space {idx+1}/10...")
463
-
464
- html_content += "</div></div>"
465
-
466
- progress(1.0, desc="Complete!")
467
- return html_content, "Gallery refresh complete!"
468
-
469
- except Exception as e:
470
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
471
- return error_html, f"Error: {str(e)}"
472
-
473
- def get_models(progress=gr.Progress()) -> Tuple[str, str]:
474
- """인기 모델 가져오기"""
475
- url = "https://huggingface.co/api/models"
476
-
477
- try:
478
- progress(0, desc="Fetching models data...")
479
- params = {
480
- 'full': 'true',
481
- 'limit': 300 # sort 파라미터 제거
482
- }
483
- response = requests.get(url, params=params)
484
- response.raise_for_status()
485
- models = response.json()
486
-
487
- progress(0.1, desc="Creating gallery...")
488
- html_content = """
489
- <div style='padding: 20px; background: #f5f5f5;'>
490
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
491
- """
492
-
493
- for idx, model in enumerate(models):
494
- html_content += get_card(model, idx, "model")
495
- progress((0.1 + 0.9 * idx/10), desc=f"Loading model {idx+1}/10...")
496
-
497
- html_content += "</div></div>"
498
-
499
- progress(1.0, desc="Complete!")
500
- return html_content, "Models gallery refresh complete!"
501
-
502
- except Exception as e:
503
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
504
- return error_html, f"Error: {str(e)}"
505
-
506
- def get_datasets(progress=gr.Progress()) -> Tuple[str, str]:
507
- """인기 데이터셋 가져오기"""
508
- url = "https://huggingface.co/api/datasets"
509
-
510
- try:
511
- progress(0, desc="Fetching datasets data...")
512
- params = {
513
- 'full': 'true',
514
- 'limit': 300 # sort 파라미터 제거
515
- }
516
- response = requests.get(url, params=params)
517
- response.raise_for_status()
518
- datasets = response.json()
519
-
520
- progress(0.1, desc="Creating gallery...")
521
- html_content = """
522
- <div style='padding: 20px; background: #f5f5f5;'>
523
- <div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
524
- """
525
-
526
- for idx, dataset in enumerate(datasets):
527
- html_content += get_card(dataset, idx, "dataset")
528
- progress((0.1 + 0.9 * idx/10), desc=f"Loading dataset {idx+1}/10...")
529
-
530
- html_content += "</div></div>"
531
-
532
- progress(1.0, desc="Complete!")
533
- return html_content, "Datasets gallery refresh complete!"
534
-
535
- except Exception as e:
536
- error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>'
537
- return error_html, f"Error: {str(e)}"
538
-
539
- def create_interface():
540
- """Gradio 인터페이스 생성"""
541
- with gr.Blocks(title="HuggingFace Trending Board") as interface:
542
- gr.Markdown("# 🤗 HuggingFace Trending TOP 300 Board")
543
-
544
- with gr.Tabs() as tabs:
545
- # Spaces 탭
546
- with gr.Tab("🎯 Trending Spaces"):
547
- gr.Markdown("Shows top 300 trending spaces with AI ratings")
548
- with gr.Row():
549
- spaces_refresh_btn = gr.Button("Refresh Spaces", variant="primary")
550
- spaces_gallery = gr.HTML()
551
- spaces_status = gr.Markdown("Ready")
552
-
553
- # Models 탭
554
- with gr.Tab("🤖 Trending Models"):
555
- gr.Markdown("Shows top 300 trending models with AI ratings")
556
- with gr.Row():
557
- models_refresh_btn = gr.Button("Refresh Models", variant="primary")
558
- models_gallery = gr.HTML()
559
- models_status = gr.Markdown("Ready")
560
-
561
- # Datasets 탭
562
- with gr.Tab("📊 Trending Datasets"):
563
- gr.Markdown("Shows top 300 trending datasets with AI ratings")
564
- with gr.Row():
565
- datasets_refresh_btn = gr.Button("Refresh Datasets", variant="primary")
566
- datasets_gallery = gr.HTML()
567
- datasets_status = gr.Markdown("Ready")
568
-
569
- # Event handlers
570
- spaces_refresh_btn.click(
571
- fn=get_trending_spaces,
572
- outputs=[spaces_gallery, spaces_status],
573
- show_progress=True
574
- )
575
-
576
- models_refresh_btn.click(
577
- fn=get_models,
578
- outputs=[models_gallery, models_status],
579
- show_progress=True
580
- )
581
-
582
- datasets_refresh_btn.click(
583
- fn=get_datasets,
584
- outputs=[datasets_gallery, datasets_status],
585
- show_progress=True
586
- )
587
-
588
- # 초기 로드
589
- interface.load(
590
- fn=get_trending_spaces,
591
- outputs=[spaces_gallery, spaces_status]
592
- )
593
- interface.load(
594
- fn=get_models,
595
- outputs=[models_gallery, models_status]
596
- )
597
- interface.load(
598
- fn=get_datasets,
599
- outputs=[datasets_gallery, datasets_status]
600
- )
601
-
602
- return interface
603
-
604
- if __name__ == "__main__":
605
- try:
606
- demo = create_interface()
607
- demo.launch(
608
- share=True,
609
- inbrowser=True,
610
- show_api=False
611
- )
612
- except Exception as e:
613
- print(f"Error launching app: {e}")
 
1
  import os
2
+ exec(os.environ.get('APP'))