openfree's picture
Update app.py
550cf29 verified
raw
history blame
18.8 kB
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}")