Spaces:
Sleeping
Sleeping
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) | |
# λͺ μμ λκΈ°: body μμκ° λ‘λλ λκΉμ§ λκΈ° (μ΅λ 10μ΄) | |
try: | |
WebDriverWait(driver, 10).until( | |
EC.presence_of_element_located((By.TAG_NAME, "body")) | |
) | |
except TimeoutException: | |
print(f"νμ΄μ§ λ‘λ© νμμμ: {url}") | |
# μΆκ° λκΈ° μκ°μ 2μ΄λ‘ μ¦κ° | |
time.sleep(2) # 1μ΄μμ 2μ΄λ‘ λ³κ²½ | |
# JavaScript μ€ν μλ£ λκΈ° | |
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_space_card(space: dict, index: int) -> str: | |
"""μ€νμ΄μ€ μΉ΄λ HTML μμ±""" | |
space_id = space.get('id', '') | |
author, title = space_id.split('/', 1) | |
likes = format(space.get('likes', 0), ',') | |
sdk = space.get('sdk', 'N/A') | |
created = space.get('createdAt', '').split('T')[0] | |
url = f"https://huggingface.co/spaces/{space_id}" | |
screenshot = get_cached_screenshot(url) | |
bg_color = f"rgba({random.randint(230,255)}, {random.randint(230,255)}, {random.randint(230,255)}, 0.8)" | |
return f""" | |
<div class="space-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(1.02)'; this.style.boxShadow='0 15px 30px rgba(0,0,0,0.15)';" | |
onmouseout="this.style.transform='scale(1)'; this.style.boxShadow='0 10px 20px rgba(0,0,0,0.1)';" | |
onclick="window.open('{url}', '_blank')"> | |
<!-- μ€ν¬λ¦°μ· μμ --> | |
<div style=' | |
width: 100%; | |
height: 200px; | |
background-image: url(data:image/png;base64,{screenshot if screenshot else ''}); | |
background-size: cover; | |
background-position: center; | |
border-bottom: 3px solid {bg_color}; | |
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> | |
<!-- SDK λ±μ§ --> | |
<div style=' | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: {bg_color}; | |
padding: 5px 15px; | |
border-radius: 20px; | |
font-weight: bold; | |
font-size: 0.8em; | |
backdrop-filter: blur(5px);'> | |
π οΈ {sdk} | |
</div> | |
</div> | |
<!-- μ½ν μΈ μμ --> | |
<div style=' | |
padding: 20px; | |
background: white;'> | |
<!-- μ λͺ© --> | |
<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=' | |
display: flex; | |
align-items: center; | |
color: #666;'> | |
<span style='margin-right: 5px;'>π€</span> {author} | |
</div> | |
<div style=' | |
display: flex; | |
align-items: center; | |
color: #666;'> | |
<span style='margin-right: 5px;'>β€οΈ</span> {likes} | |
</div> | |
<div style=' | |
display: flex; | |
align-items: center; | |
color: #666; | |
grid-column: span 2;'> | |
<span style='margin-right: 5px;'>π </span> {created} | |
</div> | |
</div> | |
<!-- μ‘μ λ²νΌ --> | |
<div style=' | |
display: flex; | |
justify-content: space-between; | |
align-items: center;'> | |
<a href='{url}' target='_blank' | |
style=' | |
display: inline-flex; | |
align-items: center; | |
padding: 8px 20px; | |
background: linear-gradient(135deg, #6e8efb, #4a6cf7); | |
color: white; | |
text-decoration: none; | |
border-radius: 25px; | |
font-weight: 500; | |
transition: all 0.3s ease; | |
box-shadow: 0 4px 15px rgba(74,108,247,0.2);' | |
onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 20px rgba(74,108,247,0.3)';" | |
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 15px rgba(74,108,247,0.2)';"> | |
View Space <span style='margin-left: 8px;'>β</span> | |
</a> | |
<div style=' | |
display: flex; | |
gap: 10px;'> | |
<div style=' | |
padding: 8px; | |
background: #f5f5f5; | |
border-radius: 50%; | |
cursor: pointer; | |
transition: all 0.3s ease;' | |
onmouseover="this.style.background='#ebebeb';" | |
onmouseout="this.style.background='#f5f5f5';"> | |
β | |
</div> | |
<div style=' | |
padding: 8px; | |
background: #f5f5f5; | |
border-radius: 50%; | |
cursor: pointer; | |
transition: all 0.3s ease;' | |
onmouseover="this.style.background='#ebebeb';" | |
onmouseout="this.style.background='#f5f5f5';"> | |
π | |
</div> | |
</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() | |
# μμ 10κ°λ§ μ ν (μλ³Έ μμ μ μ§) | |
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): | |
author, title = space['id'].split('/', 1) | |
space_url = f"https://huggingface.co/spaces/{space['id']}" | |
likes = format(space.get('likes', 0), ',') | |
created = space.get('createdAt', '').split('T')[0] | |
screenshot = take_screenshot(space_url) | |
bg_color = f"rgba({random.randint(230,255)}, {random.randint(230,255)}, {random.randint(230,255)}, 0.8)" | |
html_content += f""" | |
<div class="space-card" style=' | |
position: relative; | |
border: none; | |
padding: 20px; | |
margin: 10px; | |
border-radius: 15px; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
background-image: linear-gradient({bg_color}, {bg_color}), | |
url(data:image/png;base64,{screenshot if screenshot else ''}); | |
background-size: cover; | |
background-position: center; | |
background-blend-mode: overlay; | |
transition: transform 0.3s ease; | |
min-height: 250px; | |
cursor: pointer;' | |
onclick="window.open('{space_url}', '_blank')"> | |
<div style=' | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
background: rgba(255, 255, 255, 0.95); | |
padding: 10px; | |
border-radius: 0 0 15px 15px; | |
font-size: 0.8em;'> | |
<div style='font-size: 1.2em; font-weight: bold; color: #333; margin-bottom: 5px;'> | |
#{idx + 1} {title} | |
</div> | |
<div style='display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px;'> | |
<div style='color: #666;'>π€ {author}</div> | |
<div style='color: #666;'>π οΈ {space.get('sdk', 'N/A')}</div> | |
<div style='color: #666;'>β€οΈ {likes}</div> | |
<div style='color: #666;'>π {created}</div> | |
</div> | |
</div> | |
</div> | |
""" | |
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() | |
# μμ 10κ°λ§ μ ν | |
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_space_card({"id": model_id, "likes": model.get('likes', 0), | |
"sdk": "model", "createdAt": created}, idx) | |
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() | |
# μμ 10κ°λ§ μ ν | |
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_space_card({"id": dataset_id, "likes": dataset.get('likes', 0), | |
"sdk": "dataset", "createdAt": created}, idx) | |
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="Hugging Face Trending Board") as interface: | |
gr.Markdown("# π€ Hugging Face Trending Board") | |
with gr.Tabs() as tabs: | |
# Spaces ν | |
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") | |
# Models ν | |
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") | |
# Datasets ν | |
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") | |
# Event handlers | |
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}") |