|
import os |
|
import random |
|
import base64 |
|
import requests |
|
from selenium import webdriver |
|
from selenium.webdriver.support.ui import WebDriverWait |
|
from selenium.webdriver.support import expected_conditions as EC |
|
from selenium.webdriver.common.by import By |
|
from selenium.common.exceptions import WebDriverException, TimeoutException |
|
from PIL import Image |
|
from io import BytesIO |
|
from datetime import datetime |
|
import gradio as gr |
|
from typing import Tuple |
|
import time |
|
from pathlib import Path |
|
|
|
|
|
CACHE_DIR = Path("screenshot_cache") |
|
CACHE_DIR.mkdir(exist_ok=True) |
|
|
|
|
|
SCREENSHOT_CACHE = {} |
|
|
|
def get_cached_screenshot(url: str) -> str: |
|
"""์บ์๋ ์คํฌ๋ฆฐ์ท ๊ฐ์ ธ์ค๊ธฐ ๋๋ ์๋ก ์์ฑ""" |
|
cache_file = CACHE_DIR / f"{base64.b64encode(url.encode()).decode()}.png" |
|
|
|
if cache_file.exists(): |
|
with open(cache_file, "rb") as f: |
|
return base64.b64encode(f.read()).decode() |
|
|
|
return take_screenshot(url) |
|
|
|
def take_screenshot(url): |
|
"""์น์ฌ์ดํธ ์คํฌ๋ฆฐ์ท ์ดฌ์ ํจ์ (๋ก๋ฉ ๋๊ธฐ ์๊ฐ ์ถ๊ฐ)""" |
|
if url in SCREENSHOT_CACHE: |
|
return SCREENSHOT_CACHE[url] |
|
|
|
if not url.startswith('http'): |
|
url = f"https://{url}" |
|
|
|
options = webdriver.ChromeOptions() |
|
options.add_argument('--headless') |
|
options.add_argument('--no-sandbox') |
|
options.add_argument('--disable-dev-shm-usage') |
|
options.add_argument('--window-size=1080,720') |
|
|
|
try: |
|
driver = webdriver.Chrome(options=options) |
|
driver.get(url) |
|
|
|
|
|
try: |
|
WebDriverWait(driver, 10).until( |
|
EC.presence_of_element_located((By.TAG_NAME, "body")) |
|
) |
|
except TimeoutException: |
|
print(f"ํ์ด์ง ๋ก๋ฉ ํ์์์: {url}") |
|
|
|
|
|
time.sleep(2) |
|
|
|
|
|
driver.execute_script("return document.readyState") == "complete" |
|
|
|
|
|
screenshot = driver.get_screenshot_as_png() |
|
img = Image.open(BytesIO(screenshot)) |
|
buffered = BytesIO() |
|
img.save(buffered, format="PNG") |
|
base64_image = base64.b64encode(buffered.getvalue()).decode() |
|
|
|
|
|
SCREENSHOT_CACHE[url] = base64_image |
|
return base64_image |
|
|
|
except WebDriverException as e: |
|
print(f"์คํฌ๋ฆฐ์ท ์ดฌ์ ์คํจ: {str(e)} for URL: {url}") |
|
return None |
|
except Exception as e: |
|
print(f"์์์น ๋ชปํ ์ค๋ฅ: {str(e)} for URL: {url}") |
|
return None |
|
finally: |
|
if 'driver' in locals(): |
|
driver.quit() |
|
|
|
def get_card(item: dict, index: int, card_type: str = "space") -> str: |
|
"""ํตํฉ ์นด๋ HTML ์์ฑ""" |
|
item_id = item.get('id', '') |
|
author, title = item_id.split('/', 1) |
|
likes = format(item.get('likes', 0), ',') |
|
created = item.get('createdAt', '').split('T')[0] |
|
|
|
|
|
tags = item.get('tags', []) |
|
pipeline_tag = item.get('pipeline_tag', '') |
|
license = item.get('license', '') |
|
|
|
|
|
if card_type == "space": |
|
url = f"https://huggingface.co/spaces/{item_id}" |
|
type_icon = "๐ฏ" |
|
type_label = "SPACE" |
|
bg_content = f""" |
|
background-image: url(data:image/png;base64,{get_cached_screenshot(url) if get_cached_screenshot(url) else ''}); |
|
background-size: cover; |
|
background-position: center; |
|
""" |
|
meta_html = "" |
|
else: |
|
|
|
url = f"https://huggingface.co/{item_id}" if card_type == "model" else f"https://huggingface.co/datasets/{item_id}" |
|
type_icon = "๐ค" if card_type == "model" else "๐" |
|
type_label = "MODEL" if card_type == "model" else "DATASET" |
|
bg_color = "#6e8efb" if card_type == "model" else "#ff6b6b" |
|
|
|
meta_html = f""" |
|
<div style=' |
|
padding: 15px; |
|
background: rgba(255,255,255,0.1); |
|
border-radius: 10px; |
|
margin-top: 10px;'> |
|
|
|
<!-- Tags --> |
|
<div style=' |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 5px; |
|
margin-bottom: 10px;'> |
|
{' '.join([f''' |
|
<span style=' |
|
background: rgba(255,255,255,0.2); |
|
padding: 5px 10px; |
|
border-radius: 15px; |
|
color: white; |
|
font-size: 0.8em;'> |
|
#{tag} |
|
</span> |
|
''' for tag in tags[:5]])} |
|
</div> |
|
|
|
<!-- Pipeline Tag --> |
|
{f''' |
|
<div style=' |
|
color: white; |
|
margin-bottom: 5px; |
|
font-size: 0.9em;'> |
|
๐ง Pipeline: {pipeline_tag} |
|
</div> |
|
''' if pipeline_tag else ''} |
|
|
|
<!-- License --> |
|
{f''' |
|
<div style=' |
|
color: white; |
|
font-size: 0.9em;'> |
|
๐ License: {license} |
|
</div> |
|
''' if license else ''} |
|
</div> |
|
""" |
|
|
|
bg_content = f""" |
|
background: linear-gradient(135deg, {bg_color}, {bg_color}dd); |
|
padding: 15px; |
|
color: white; |
|
""" |
|
|
|
return f""" |
|
<div class="card" style=' |
|
position: relative; |
|
border: none; |
|
padding: 0; |
|
margin: 10px; |
|
border-radius: 20px; |
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1); |
|
background: white; |
|
transition: all 0.3s ease; |
|
overflow: hidden; |
|
min-height: 400px; |
|
cursor: pointer; |
|
transform-origin: center;' |
|
onmouseover="this.style.transform='scale(0.98) translateY(5px)'; this.style.boxShadow='0 5px 15px rgba(0,0,0,0.2)';" |
|
onmouseout="this.style.transform='scale(1) translateY(0)'; this.style.boxShadow='0 10px 20px rgba(0,0,0,0.1)';" |
|
onclick="window.open('{url}', '_blank')"> |
|
|
|
<!-- ์๋จ ์์ญ --> |
|
<div style=' |
|
width: 100%; |
|
height: 200px; |
|
{bg_content} |
|
position: relative;'> |
|
|
|
<!-- ์์ ๋ฑ์ง --> |
|
<div style=' |
|
position: absolute; |
|
top: 10px; |
|
left: 10px; |
|
background: rgba(0,0,0,0.7); |
|
color: white; |
|
padding: 5px 15px; |
|
border-radius: 20px; |
|
font-weight: bold; |
|
font-size: 0.9em; |
|
backdrop-filter: blur(5px);'> |
|
#{index + 1} |
|
</div> |
|
|
|
<!-- ํ์
๋ฑ์ง --> |
|
<div style=' |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
background: rgba(255,255,255,0.9); |
|
padding: 5px 15px; |
|
border-radius: 20px; |
|
font-weight: bold; |
|
font-size: 0.8em;'> |
|
{type_icon} {type_label} |
|
</div> |
|
|
|
{meta_html} |
|
</div> |
|
|
|
<!-- ์ฝํ
์ธ ์์ญ --> |
|
<div style=' |
|
padding: 20px; |
|
background: {("#f5f5f5" if card_type == "space" else "white")}; |
|
border-radius: 0 0 20px 20px;'> |
|
<h3 style=' |
|
margin: 0 0 15px 0; |
|
color: #333; |
|
font-size: 1.3em; |
|
line-height: 1.4; |
|
display: -webkit-box; |
|
-webkit-line-clamp: 2; |
|
-webkit-box-orient: vertical; |
|
overflow: hidden; |
|
text-overflow: ellipsis;'> |
|
{title} |
|
</h3> |
|
|
|
<div style=' |
|
display: grid; |
|
grid-template-columns: repeat(2, 1fr); |
|
gap: 10px; |
|
margin-bottom: 20px; |
|
font-size: 0.9em;'> |
|
<div style='color: #666;'> |
|
<span style='margin-right: 5px;'>๐ค</span> {author} |
|
</div> |
|
<div style='color: #666;'> |
|
<span style='margin-right: 5px;'>โค๏ธ</span> {likes} |
|
</div> |
|
<div style='color: #666; grid-column: span 2;'> |
|
<span style='margin-right: 5px;'>๐
</span> {created} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
def get_trending_spaces(progress=gr.Progress()) -> Tuple[str, str]: |
|
"""ํธ๋ ๋ฉ ์คํ์ด์ค ๊ฐ์ ธ์ค๊ธฐ""" |
|
url = "https://huggingface.co/api/spaces" |
|
|
|
try: |
|
progress(0, desc="Fetching spaces data...") |
|
response = requests.get(url) |
|
response.raise_for_status() |
|
spaces = response.json() |
|
|
|
|
|
top_spaces = spaces[:10] |
|
|
|
progress(0.1, desc="Creating gallery...") |
|
html_content = """ |
|
<div style='padding: 20px; background: #f5f5f5;'> |
|
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'> |
|
""" |
|
|
|
for idx, space in enumerate(top_spaces): |
|
html_content += get_card(space, idx, "space") |
|
progress((0.1 + 0.9 * idx/10), desc=f"Loading space {idx+1}/10...") |
|
|
|
html_content += "</div></div>" |
|
|
|
progress(1.0, desc="Complete!") |
|
return html_content, "Gallery refresh complete!" |
|
|
|
except Exception as e: |
|
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>' |
|
return error_html, f"Error: {str(e)}" |
|
|
|
def get_models(progress=gr.Progress()) -> Tuple[str, str]: |
|
"""์ธ๊ธฐ ๋ชจ๋ธ ๊ฐ์ ธ์ค๊ธฐ""" |
|
url = "https://huggingface.co/api/models" |
|
|
|
try: |
|
progress(0, desc="Fetching models data...") |
|
response = requests.get(url) |
|
response.raise_for_status() |
|
models = response.json() |
|
|
|
|
|
top_models = models[:10] |
|
|
|
progress(0.1, desc="Creating gallery...") |
|
|
|
html_content = """ |
|
<div style='padding: 20px; background: #f5f5f5;'> |
|
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'> |
|
""" |
|
|
|
for idx, model in enumerate(top_models): |
|
model_id = model.get('id', '') |
|
author = model_id.split('/')[0] |
|
title = model_id.split('/')[-1] |
|
likes = format(model.get('likes', 0), ',') |
|
downloads = format(model.get('downloads', 0), ',') |
|
created = model.get('createdAt', '').split('T')[0] |
|
url = f"https://huggingface.co/{model_id}" |
|
|
|
screenshot = get_cached_screenshot(url) |
|
html_content += get_card(model, idx, "model") |
|
|
|
|
|
|
|
progress((0.1 + 0.9 * idx/10), desc=f"Loading model {idx+1}/10...") |
|
|
|
html_content += "</div></div>" |
|
|
|
progress(1.0, desc="Complete!") |
|
return html_content, "Models gallery refresh complete!" |
|
|
|
except Exception as e: |
|
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>' |
|
return error_html, f"Error: {str(e)}" |
|
|
|
def get_datasets(progress=gr.Progress()) -> Tuple[str, str]: |
|
"""์ธ๊ธฐ ๋ฐ์ดํฐ์
๊ฐ์ ธ์ค๊ธฐ""" |
|
url = "https://huggingface.co/api/datasets" |
|
|
|
try: |
|
progress(0, desc="Fetching datasets data...") |
|
response = requests.get(url) |
|
response.raise_for_status() |
|
datasets = response.json() |
|
|
|
|
|
top_datasets = datasets[:10] |
|
|
|
progress(0.1, desc="Creating gallery...") |
|
html_content = """ |
|
<div style='padding: 20px; background: #f5f5f5;'> |
|
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'> |
|
""" |
|
|
|
for idx, dataset in enumerate(top_datasets): |
|
dataset_id = dataset.get('id', '') |
|
author = dataset_id.split('/')[0] |
|
title = dataset_id.split('/')[-1] |
|
likes = format(dataset.get('likes', 0), ',') |
|
downloads = format(dataset.get('downloads', 0), ',') |
|
created = dataset.get('createdAt', '').split('T')[0] |
|
url = f"https://huggingface.co/datasets/{dataset_id}" |
|
|
|
screenshot = get_cached_screenshot(url) |
|
html_content += get_card(dataset, idx, "dataset") |
|
|
|
|
|
|
|
progress((0.1 + 0.9 * idx/10), desc=f"Loading dataset {idx+1}/10...") |
|
|
|
html_content += "</div></div>" |
|
|
|
progress(1.0, desc="Complete!") |
|
return html_content, "Datasets gallery refresh complete!" |
|
|
|
except Exception as e: |
|
error_html = f'<div style="color: red; padding: 20px;">Error: {str(e)}</div>' |
|
return error_html, f"Error: {str(e)}" |
|
|
|
def create_interface(): |
|
"""Gradio ์ธํฐํ์ด์ค ์์ฑ""" |
|
with gr.Blocks(title="HuggingFace Trending Board") as interface: |
|
gr.Markdown("# ๐ค HuggingFace Trending Board") |
|
|
|
with gr.Tabs() as tabs: |
|
|
|
with gr.Tab("๐ฏ Trending Spaces"): |
|
gr.Markdown("Shows top 10 trending spaces on Hugging Face") |
|
with gr.Row(): |
|
spaces_refresh_btn = gr.Button("Refresh Spaces", variant="primary") |
|
spaces_gallery = gr.HTML() |
|
spaces_status = gr.Markdown("Ready") |
|
|
|
|
|
with gr.Tab("๐ค Trending Models"): |
|
gr.Markdown("Shows top 10 trending models on Hugging Face") |
|
with gr.Row(): |
|
models_refresh_btn = gr.Button("Refresh Models", variant="primary") |
|
models_gallery = gr.HTML() |
|
models_status = gr.Markdown("Ready") |
|
|
|
|
|
with gr.Tab("๐ Trending Datasets"): |
|
gr.Markdown("Shows top 10 trending datasets on Hugging Face") |
|
with gr.Row(): |
|
datasets_refresh_btn = gr.Button("Refresh Datasets", variant="primary") |
|
datasets_gallery = gr.HTML() |
|
datasets_status = gr.Markdown("Ready") |
|
|
|
|
|
spaces_refresh_btn.click( |
|
fn=get_trending_spaces, |
|
outputs=[spaces_gallery, spaces_status], |
|
show_progress=True |
|
) |
|
|
|
models_refresh_btn.click( |
|
fn=get_models, |
|
outputs=[models_gallery, models_status], |
|
show_progress=True |
|
) |
|
|
|
datasets_refresh_btn.click( |
|
fn=get_datasets, |
|
outputs=[datasets_gallery, datasets_status], |
|
show_progress=True |
|
) |
|
|
|
|
|
interface.load( |
|
fn=get_trending_spaces, |
|
outputs=[spaces_gallery, spaces_status] |
|
) |
|
interface.load( |
|
fn=get_models, |
|
outputs=[models_gallery, models_status] |
|
) |
|
interface.load( |
|
fn=get_datasets, |
|
outputs=[datasets_gallery, datasets_status] |
|
) |
|
|
|
return interface |
|
|
|
if __name__ == "__main__": |
|
try: |
|
demo = create_interface() |
|
demo.launch( |
|
share=True, |
|
inbrowser=True, |
|
show_api=False |
|
) |
|
except Exception as e: |
|
print(f"Error launching app: {e}") |