Spaces:
Building
on
CPU Upgrade
Building
on
CPU Upgrade
import os | |
import random | |
import base64 | |
import requests | |
import tempfile | |
import shutil | |
import time | |
import numpy as np | |
import traceback | |
from typing import List, Tuple | |
from datetime import datetime, timedelta | |
from pathlib import Path | |
from io import BytesIO | |
from urllib.parse import urljoin | |
# Selenium ๊ด๋ จ | |
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, ImageDraw, ImageFont | |
# Gradio | |
import gradio as gr | |
# HuggingFace | |
from huggingface_hub import InferenceClient | |
from dotenv import load_dotenv | |
# HTML ํ์ฑ | |
from bs4 import BeautifulSoup | |
# ์์ฑ ๋ฐ ๋น๋์ค ์ฒ๋ฆฌ | |
from gtts import gTTS | |
from moviepy.editor import ( | |
VideoFileClip, | |
AudioFileClip, | |
ImageClip, | |
concatenate_videoclips | |
) | |
# ์๋จ์ import ์ถ๊ฐ | |
import textwrap | |
# .env ํ์ผ์์ ํ๊ฒฝ ๋ณ์ ๋ก๋ | |
load_dotenv() | |
# HuggingFace ์ธํผ๋ฐ์ค ํด๋ผ์ด์ธํธ ์ค์ | |
hf_client = InferenceClient( | |
"CohereForAI/c4ai-command-r-plus-08-2024", | |
token=os.getenv("HF_TOKEN") | |
) | |
# ์คํฌ๋ฆฐ์ท ์บ์ ๋๋ ํ ๋ฆฌ ์ค์ | |
CACHE_DIR = Path("screenshot_cache") | |
CACHE_DIR.mkdir(exist_ok=True) | |
# ์ ์ญ ๋ณ์๋ก ์คํฌ๋ฆฐ์ท ์บ์ ์ ์ธ | |
SCREENSHOT_CACHE = {} | |
def get_cached_screenshot(url: str) -> str: | |
"""์บ์๋ ์คํฌ๋ฆฐ์ท ๊ฐ์ ธ์ค๊ธฐ ๋๋ ์๋ก ์์ฑ""" | |
try: | |
# URL์ ์์ ํ ํ์ผ๋ช ์ผ๋ก ๋ณํ | |
safe_filename = base64.urlsafe_b64encode(url.encode()).decode() | |
cache_file = CACHE_DIR / f"{safe_filename[:200]}.jpg" # PNG ๋์ JPG ์ฌ์ฉ | |
if cache_file.exists(): | |
try: | |
with Image.open(cache_file) as img: | |
buffered = BytesIO() | |
img.save(buffered, format="JPEG", quality=85, optimize=True) | |
return base64.b64encode(buffered.getvalue()).decode() | |
except Exception as e: | |
print(f"Cache read error for {url}: {e}") | |
if cache_file.exists(): | |
cache_file.unlink() | |
return take_screenshot(url) | |
except Exception as e: | |
print(f"Screenshot cache error for {url}: {e}") | |
return "" | |
def take_screenshot(url: str) -> str: | |
"""์น์ฌ์ดํธ ์คํฌ๋ฆฐ์ท ์ดฌ์""" | |
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') | |
driver = None | |
try: | |
driver = webdriver.Chrome(options=options) | |
driver.get(url) | |
# ํ์ด์ง ๋ก๋ฉ ๋๊ธฐ | |
WebDriverWait(driver, 15).until( | |
EC.presence_of_element_located((By.TAG_NAME, "body")) | |
) | |
# ์ถ๊ฐ ๋๊ธฐ ์๊ฐ | |
time.sleep(3) | |
# ์คํฌ๋ฆฐ์ท ์ดฌ์ ๋ฐ ์ต์ ํ | |
screenshot = driver.get_screenshot_as_png() | |
img = Image.open(BytesIO(screenshot)) | |
# ์ด๋ฏธ์ง ํฌ๊ธฐ ์ต์ ํ | |
max_size = (800, 600) | |
img.thumbnail(max_size, Image.Resampling.LANCZOS) | |
# JPEG๋ก ๋ณํ ๋ฐ ์ต์ ํ | |
if img.mode in ('RGBA', 'LA'): | |
background = Image.new('RGB', img.size, (255, 255, 255)) | |
background.paste(img, mask=img.split()[-1]) | |
img = background | |
# ์บ์ ์ ์ฅ | |
safe_filename = base64.urlsafe_b64encode(url.encode()).decode() | |
cache_file = CACHE_DIR / f"{safe_filename[:200]}.jpg" | |
img.save(cache_file, format="JPEG", quality=85, optimize=True) | |
# ๋ฐํ์ฉ ์ด๋ฏธ์ง ์์ฑ | |
buffered = BytesIO() | |
img.save(buffered, format="JPEG", quality=85, optimize=True) | |
return base64.b64encode(buffered.getvalue()).decode() | |
except Exception as e: | |
print(f"Screenshot error for {url}: {e}") | |
return "" | |
finally: | |
if driver: | |
driver.quit() | |
def cleanup_cache(): | |
"""์บ์ ์ ๋ฆฌ""" | |
try: | |
current_time = time.time() | |
for cache_file in CACHE_DIR.glob("*.jpg"): | |
try: | |
# 24์๊ฐ ์ด์ ๋ ํ์ผ ๋๋ 0๋ฐ์ดํธ ํ์ผ ์ญ์ | |
if (current_time - cache_file.stat().st_mtime > 86400) or cache_file.stat().st_size == 0: | |
cache_file.unlink() | |
except Exception as e: | |
print(f"Error cleaning cache file {cache_file}: {e}") | |
except Exception as e: | |
print(f"Cache cleanup error: {e}") | |
# ์ฑ ์์ ์ ์บ์ ์ ๋ฆฌ | |
cleanup_cache() | |
def calculate_rising_rate(created_date: str, rank: int) -> int: | |
"""AI Rising Rate ๊ณ์ฐ""" | |
# ์์ฑ์ผ ๊ธฐ์ค ์ ์ ๊ณ์ฐ | |
created = datetime.strptime(created_date.split('T')[0], '%Y-%m-%d') | |
today = datetime.now() | |
days_diff = (today - created).days | |
date_score = max(0, 300 - days_diff) # ์ต๋ 300์ | |
# ์์ ๊ธฐ์ค ์ ์ ๊ณ์ฐ | |
rank_score = max(0, 600 - rank) # ์ต๋ 300์ | |
# ์ด์ ๊ณ์ฐ | |
total_score = date_score + rank_score | |
# ๋ณ ๊ฐ์ ๊ณ์ฐ (0~5) | |
if total_score <= 200: | |
stars = 1 | |
elif total_score <= 400: | |
stars = 2 | |
elif total_score <= 600: | |
stars = 3 | |
elif total_score <= 800: | |
stars = 4 | |
else: | |
stars = 5 | |
return stars | |
def get_popularity_grade(likes: int, stars: int) -> tuple: | |
"""AI Popularity Score ๋ฑ๊ธ ๊ณ์ฐ""" | |
# ๊ธฐ๋ณธ ์ ์ (likes) | |
base_score = min(likes, 10000) # ์ต๋ 10000์ | |
# ๋ณ์ ์ถ๊ฐ ์ ์ (๋ณ ํ๋๋น 500์ ) | |
star_score = stars * 1000 | |
# ์ด์ | |
total_score = base_score + star_score | |
# ๋ฑ๊ธ ํ ์ด๋ธ (18๋จ๊ณ) | |
grades = [ | |
(14500, "AAA+"), (14000, "AAA"), (13500, "AAA-"), | |
(13000, "AA+"), (12500, "AA"), (12000, "AA-"), | |
(11500, "A+"), (11000, "A"), (10000, "A-"), | |
(9000, "BBB+"), (8000, "BBB"), (7000, "BBB-"), | |
(6000, "BB+"), (5000, "BB"), (4000, "BB-"), | |
(3000, "B+"), (2000, "B"), (1000, "B-") | |
] | |
for threshold, grade in grades: | |
if total_score >= threshold: | |
return grade, total_score | |
return "B-", total_score | |
# get_card ํจ์ ๋ด์ hardware_info ๋ถ๋ถ์ ๋ค์์ผ๋ก ๊ต์ฒด: | |
def get_rating_info(item: dict, index: int) -> str: | |
"""ํ๊ฐ ์ ๋ณด HTML ์์ฑ""" | |
created = item.get('createdAt', '').split('T')[0] | |
likes = int(str(item.get('likes', '0')).replace(',', '')) | |
# AI Rising Rate ๊ณ์ฐ | |
stars = calculate_rising_rate(created, index + 1) | |
star_html = "โ " * stars + "โ" * (5 - stars) # ์ฑ์์ง ๋ณ๊ณผ ๋น ๋ณ ์กฐํฉ | |
# AI Popularity Score ๊ณ์ฐ | |
grade, score = get_popularity_grade(likes, stars) | |
# ๋ฑ๊ธ๋ณ ์์ ์ค์ | |
grade_colors = { | |
'AAA': '#FFD700', 'AA': '#FFA500', 'A': '#FF4500', | |
'BBB': '#4169E1', 'BB': '#1E90FF', 'B': '#00BFFF' | |
} | |
grade_base = grade.rstrip('+-') | |
grade_color = grade_colors.get(grade_base, '#666666') | |
return f""" | |
<div style=' | |
margin-top: 15px; | |
padding: 15px; | |
background: rgba(255,255,255,0.4); | |
border-radius: 10px; | |
font-size: 0.9em; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1);'> | |
<div style=' | |
display: grid; | |
grid-template-columns: repeat(2, 1fr); | |
gap: 15px;'> | |
<div style=' | |
color: #333; | |
display: flex; | |
flex-direction: column; | |
gap: 5px;'> | |
<span style='font-weight: bold;'>AI Rising Rate:</span> | |
<span style=' | |
color: #FF8C00; | |
font-size: 1.4em; | |
letter-spacing: 2px; | |
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{star_html}</span> | |
</div> | |
<div style=' | |
color: #333; | |
display: flex; | |
flex-direction: column; | |
gap: 5px;'> | |
<span style='font-weight: bold;'>AI Popularity Score:</span> | |
<span style=' | |
font-size: 1.2em; | |
font-weight: bold; | |
color: {grade_color}; | |
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);'>{grade} ({score:,})</span> | |
</div> | |
</div> | |
</div> | |
""" | |
def get_hardware_info(item: dict) -> tuple: | |
"""ํ๋์จ์ด ์ ๋ณด ์ถ์ถ""" | |
try: | |
# runtime ์ ๋ณด ํ์ธ | |
runtime = item.get('runtime', {}) | |
# CPU ์ ๋ณด ์ฒ๋ฆฌ | |
cpu_info = runtime.get('cpu', 'Standard') | |
# GPU ์ ๋ณด ์ฒ๋ฆฌ | |
gpu_info = "None" | |
if runtime.get('accelerator') == "gpu": | |
gpu_type = runtime.get('gpu', {}).get('name', '') | |
gpu_memory = runtime.get('gpu', {}).get('memory', '') | |
if gpu_type: | |
gpu_info = f"{gpu_type}" | |
if gpu_memory: | |
gpu_info += f" ({gpu_memory}GB)" | |
# spaces decorator ํ์ธ | |
if '@spaces.GPU' in str(item.get('sdk_version', '')): | |
if gpu_info == "None": | |
gpu_info = "GPU Enabled" | |
# SDK ์ ๋ณด ์ฒ๋ฆฌ | |
sdk = item.get('sdk', 'N/A') | |
print(f"Debug - Runtime Info: {runtime}") # ๋๋ฒ๊ทธ ์ถ๋ ฅ | |
print(f"Debug - GPU Info: {gpu_info}") # ๋๋ฒ๊ทธ ์ถ๋ ฅ | |
return cpu_info, gpu_info, sdk | |
except Exception as e: | |
print(f"Error parsing hardware info: {str(e)}") | |
return 'Standard', 'None', 'N/A' | |
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] | |
# short_description ๊ฐ์ ธ์ค๊ธฐ | |
short_description = item.get('cardData', {}).get('short_description', '') | |
# URL ์ ์ | |
if card_type == "space": | |
url = f"https://huggingface.co/spaces/{item_id}" | |
elif card_type == "model": | |
url = f"https://huggingface.co/{item_id}" | |
else: # dataset | |
url = f"https://huggingface.co/datasets/{item_id}" | |
# ๋ฉํ๋ฐ์ดํฐ ์ฒ๋ฆฌ | |
tags = item.get('tags', []) | |
pipeline_tag = item.get('pipeline_tag', '') | |
license = item.get('license', '') | |
sdk = item.get('sdk', 'N/A') | |
# AI Rating ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ | |
rating_info = get_rating_info(item, index) | |
# ์นด๋ ํ์ ๋ณ ๊ทธ๋ผ๋ฐ์ด์ ์ค์ | |
if card_type == "space": | |
gradient_colors = """ | |
rgba(255, 182, 193, 0.7), /* ํ์คํ ํํฌ */ | |
rgba(173, 216, 230, 0.7), /* ํ์คํ ๋ธ๋ฃจ */ | |
rgba(255, 218, 185, 0.7) /* ํ์คํ ํผ์น */ | |
""" | |
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; | |
""" | |
type_icon = "๐ฏ" | |
type_label = "SPACE" | |
elif card_type == "model": | |
gradient_colors = """ | |
rgba(110, 142, 251, 0.7), /* ๋ชจ๋ธ ๋ธ๋ฃจ */ | |
rgba(130, 158, 251, 0.7), | |
rgba(150, 174, 251, 0.7) | |
""" | |
bg_content = f""" | |
background: linear-gradient(135deg, #6e8efb, #4a6cf7); | |
padding: 15px; | |
""" | |
type_icon = "๐ค" | |
type_label = "MODEL" | |
else: # dataset | |
gradient_colors = """ | |
rgba(255, 107, 107, 0.7), /* ๋ฐ์ดํฐ์ ๋ ๋ */ | |
rgba(255, 127, 127, 0.7), | |
rgba(255, 147, 147, 0.7) | |
""" | |
bg_content = f""" | |
background: linear-gradient(135deg, #ff6b6b, #ff8787); | |
padding: 15px; | |
""" | |
type_icon = "๐" | |
type_label = "DATASET" | |
content_bg = f""" | |
background: linear-gradient(135deg, {gradient_colors}); | |
backdrop-filter: blur(10px); | |
""" | |
# ํ๊ทธ ํ์ (models์ datasets์ฉ) | |
tags_html = "" | |
if card_type != "space": | |
tags_html = f""" | |
<div style=' | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
display: flex; | |
flex-wrap: wrap; | |
gap: 5px; | |
justify-content: center; | |
width: 90%;'> | |
{' '.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> | |
""" | |
# ์นด๋ HTML ๋ฐํ | |
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> | |
{tags_html} | |
</div> | |
<!-- ์ฝํ ์ธ ์์ญ --> | |
<div style=' | |
padding: 20px; | |
{content_bg} | |
border-radius: 0 0 20px 20px; | |
border-top: 1px solid rgba(255,255,255,0.5);'> | |
<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; | |
text-shadow: 1px 1px 1px rgba(255,255,255,0.8);'> | |
{title} | |
</h3> | |
{f''' | |
<!-- Short Description (Space ์นด๋์๋ง ํ์) --> | |
<div style=' | |
margin: 0 0 15px 0; | |
color: #444; | |
font-size: 0.9em; | |
line-height: 1.5; | |
display: -webkit-box; | |
-webkit-line-clamp: 3; | |
-webkit-box-orient: vertical; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
background: rgba(255,255,255,0.4); | |
padding: 10px; | |
border-radius: 8px;'> | |
{short_description} | |
</div> | |
''' if card_type == "space" and short_description else ''} | |
<div style=' | |
display: grid; | |
grid-template-columns: repeat(2, 1fr); | |
gap: 10px; | |
font-size: 0.9em; | |
background: rgba(255,255,255,0.3); | |
padding: 10px; | |
border-radius: 10px;'> | |
<div style='color: #444;'> | |
<span style='margin-right: 5px;'>๐ค</span> {author} | |
</div> | |
<div style='color: #444;'> | |
<span style='margin-right: 5px;'>โค๏ธ</span> {likes} | |
</div> | |
<div style='color: #444; grid-column: span 2;'> | |
<span style='margin-right: 5px;'>๐ </span> {created} | |
</div> | |
</div> | |
{rating_info} | |
</div> | |
</div> | |
""" | |
def get_trending_spaces(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]: | |
"""ํธ๋ ๋ฉ ์คํ์ด์ค ๊ฐ์ ธ์ค๊ธฐ""" | |
url = "https://huggingface.co/api/spaces" | |
try: | |
progress(0, desc="Fetching spaces data...") | |
params = { | |
'full': 'true', | |
'limit': 24 | |
} | |
response = requests.get(url, params=params) | |
response.raise_for_status() | |
spaces = response.json() | |
# ๊ฒ์์ด๋ก ํํฐ๋ง | |
if search_query: | |
spaces = [space for space in spaces if search_query.lower() in | |
(space.get('id', '') + ' ' + space.get('title', '')).lower()] | |
# ์ ๋ ฌ | |
sort_by = sort_by.lower() | |
if sort_by == "rising_rate": | |
spaces.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True) | |
elif sort_by == "popularity": | |
spaces.sort(key=lambda x: get_popularity_grade( | |
int(str(x.get('likes', '0')).replace(',', '')), | |
calculate_rising_rate(x.get('createdAt', ''), 0))[1], | |
reverse=True) | |
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(spaces): | |
html_content += get_card(space, idx, "space") | |
progress((0.1 + 0.9 * idx/len(spaces)), desc=f"Loading space {idx+1}/{len(spaces)}...") | |
html_content += "</div></div>" | |
progress(1.0, desc="Complete!") | |
return html_content, f"Found {len(spaces)} spaces" | |
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(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]: | |
"""์ธ๊ธฐ ๋ชจ๋ธ ๊ฐ์ ธ์ค๊ธฐ""" | |
url = "https://huggingface.co/api/models" | |
try: | |
progress(0, desc="Fetching models data...") | |
params = { | |
'full': 'true', | |
'limit': 300 | |
} | |
response = requests.get(url, params=params) | |
response.raise_for_status() | |
models = response.json() | |
# ๊ฒ์์ด๋ก ํํฐ๋ง | |
if search_query: | |
models = [model for model in models if search_query.lower() in | |
(model.get('id', '') + ' ' + model.get('title', '')).lower()] | |
# ์ ๋ ฌ | |
sort_by = sort_by.lower() | |
if sort_by == "rising_rate": | |
models.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True) | |
elif sort_by == "popularity": | |
models.sort(key=lambda x: get_popularity_grade( | |
int(str(x.get('likes', '0')).replace(',', '')), | |
calculate_rising_rate(x.get('createdAt', ''), 0))[1], | |
reverse=True) | |
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(models): | |
html_content += get_card(model, idx, "model") | |
progress((0.1 + 0.9 * idx/len(models)), desc=f"Loading model {idx+1}/{len(models)}...") | |
html_content += "</div></div>" | |
progress(1.0, desc="Complete!") | |
return html_content, f"Found {len(models)} models" | |
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(search_query="", sort_by="rank", progress=gr.Progress()) -> Tuple[str, str]: | |
"""์ธ๊ธฐ ๋ฐ์ดํฐ์ ๊ฐ์ ธ์ค๊ธฐ""" | |
url = "https://huggingface.co/api/datasets" | |
try: | |
progress(0, desc="Fetching datasets data...") | |
params = { | |
'full': 'true', | |
'limit': 300 | |
} | |
response = requests.get(url, params=params) | |
response.raise_for_status() | |
datasets = response.json() | |
# ๊ฒ์์ด๋ก ํํฐ๋ง | |
if search_query: | |
datasets = [dataset for dataset in datasets if search_query.lower() in | |
(dataset.get('id', '') + ' ' + dataset.get('title', '')).lower()] | |
# ์ ๋ ฌ | |
sort_by = sort_by.lower() | |
if sort_by == "rising_rate": | |
datasets.sort(key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True) | |
elif sort_by == "popularity": | |
datasets.sort(key=lambda x: get_popularity_grade( | |
int(str(x.get('likes', '0')).replace(',', '')), | |
calculate_rising_rate(x.get('createdAt', ''), 0))[1], | |
reverse=True) | |
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(datasets): | |
html_content += get_card(dataset, idx, "dataset") | |
progress((0.1 + 0.9 * idx/len(datasets)), desc=f"Loading dataset {idx+1}/{len(datasets)}...") | |
html_content += "</div></div>" | |
progress(1.0, desc="Complete!") | |
return html_content, f"Found {len(datasets)} datasets" | |
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 sort_items(items, sort_by): | |
if sort_by == "rank": | |
return items # ์ด๋ฏธ ์์๋๋ก ์ ๋ ฌ๋์ด ์์ | |
elif sort_by == "rising_rate": | |
return sorted(items, key=lambda x: calculate_rising_rate(x.get('createdAt', ''), 0), reverse=True) | |
elif sort_by == "popularity": | |
return sorted(items, key=lambda x: get_popularity_grade(int(str(x.get('likes', '0')).replace(',', '')), | |
calculate_rising_rate(x.get('createdAt', ''), 0))[1], reverse=True) | |
return items | |
# API ํธ์ถ ํจ์ ์์ | |
def fetch_items(item_type, search_query="", sort_by="rank", limit=1000): | |
"""์์ดํ ๊ฐ์ ธ์ค๊ธฐ (spaces/models/datasets)""" | |
base_url = f"https://huggingface.co/api/{item_type}" | |
params = { | |
'full': 'true', | |
'limit': limit, | |
'search': search_query | |
} | |
try: | |
response = requests.get(base_url, params=params) | |
response.raise_for_status() | |
items = response.json() | |
# ๊ฒ์์ด๋ก ํํฐ๋ง | |
if search_query: | |
items = [item for item in items if search_query.lower() in | |
(item.get('id', '') + item.get('title', '')).lower()] | |
# ์ ๋ ฌ | |
items = sort_items(items, sort_by) | |
return items[:300] # ์์ 300๊ฐ๋ง ๋ฐํ | |
except Exception as e: | |
print(f"Error fetching items: {e}") | |
return [] | |
def get_space_source(space_id: str) -> dict: | |
"""์คํ์ด์ค์ ์์ค์ฝ๋ ๊ฐ์ ธ์ค๊ธฐ""" | |
try: | |
headers = {"Authorization": f"Bearer {os.getenv('HF_TOKEN')}"} | |
files_to_try = [ | |
'app.py', | |
'index.html', | |
'app.js', | |
'main.py', | |
'streamlit_app.py', | |
'gradio_ui.py' | |
] | |
source = {} | |
for file in files_to_try: | |
url = f"https://huggingface.co/spaces/{space_id}/raw/main/{file}" | |
response = requests.get(url, headers=headers) | |
if response.status_code == 200: | |
source[file] = response.text | |
return source if source else {"app.py": "", "index.html": ""} | |
except Exception as e: | |
print(f"Error fetching source for {space_id}: {str(e)}") | |
return {"app.py": "", "index.html": ""} | |
def analyze_spaces(progress=gr.Progress()): | |
"""์คํ์ด์ค ๋ถ์ ๋ฐ HTML ์์ฑ""" | |
try: | |
url = "https://huggingface.co/api/spaces" | |
response = requests.get(url, params={'full': 'true', 'limit': 24}) | |
response.raise_for_status() | |
spaces = response.json()[:24] | |
html_content = "<div style='padding: 20px;'>" | |
for idx, space in enumerate(spaces): | |
progress((idx + 1) / 24, desc=f"๋ถ์ ์ค... {idx+1}/24") | |
try: | |
# ์คํฌ๋ฆฐ์ท ์ฒ๋ฆฌ | |
space_url = f"https://huggingface.co/spaces/{space['id']}" | |
screenshot_base64 = get_cached_screenshot(space_url) | |
# ํ ์คํธ ๋ถ์ | |
project_name = space['id'].split('/')[-1] | |
source = get_space_source(space['id']) | |
source_code = source["app.py"] or source["index.html"] | |
prompt = f""" | |
๋ค์ ์คํ์ด์ค๋ฅผ ๊ฐ๋จํ ์ค๋ช ํด์ฃผ์ธ์: | |
์คํ์ด์ค ์ด๋ฆ: {project_name} | |
์์: {idx + 1}์ | |
๋ค์ ํ์์ผ๋ก ์์ฑ: | |
1. ์์์ ์ด๋ฆ ์๊ฐ | |
2. ์ฃผ์ ๊ธฐ๋ฅ ์ค๋ช | |
3. ํน์ง์ ์ธ ์ฅ์ | |
""" | |
messages = [ | |
{"role": "system", "content": "๊ฐ๋จ๋ช ๋ฃํ ์ค๋ช ์ ์ ๊ณตํ๋ ๋ฆฌํฌํฐ์ ๋๋ค."}, | |
{"role": "user", "content": prompt} | |
] | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=150, | |
temperature=0.7 | |
) | |
analysis = response.choices[0].message.content.strip() | |
# HTML ์นด๋ ์์ฑ | |
html_content += f""" | |
<div style=' | |
background: white; | |
border-radius: 10px; | |
padding: 20px; | |
margin-bottom: 20px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);'> | |
<div style='display: flex; gap: 20px;'> | |
<div style='flex: 2;'> | |
<h3>Space #{idx + 1}</h3> | |
<textarea style=' | |
width: 100%; | |
min-height: 100px; | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
margin-top: 10px;' | |
>{analysis}</textarea> | |
</div> | |
<div style='flex: 1;'> | |
{f'<img src="data:image/jpeg;base64,{screenshot_base64}" style="width: 100%; border-radius: 5px;">' if screenshot_base64 else ''} | |
</div> | |
</div> | |
</div> | |
""" | |
except Exception as e: | |
print(f"Error processing space {space['id']}: {e}") | |
html_content += f""" | |
<div style=' | |
background: white; | |
border-radius: 10px; | |
padding: 20px; | |
margin-bottom: 20px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1);'> | |
<h3>Space #{idx + 1}</h3> | |
<p>๋ถ์์ ์ค๋น์ค์ ๋๋ค.</p> | |
</div> | |
""" | |
html_content += "</div>" | |
return html_content | |
except Exception as e: | |
print(f"Analysis error: {e}") | |
return "<div style='color: red; padding: 20px;'>๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</div>" | |
def analyze_top_spaces(progress=gr.Progress()) -> Tuple[str, str]: | |
"""์์ 24๊ฐ ์คํ์ด์ค ๋ถ์""" | |
try: | |
progress(0, desc="์คํ์ด์ค ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ์ค...") | |
url = "https://huggingface.co/api/spaces" | |
response = requests.get(url, params={'full': 'true', 'limit': 24}) | |
response.raise_for_status() | |
spaces = response.json()[:24] | |
# ์๋จ ์ ๋ ฅ ๋ฐ์ค์ ๊ธฐ๋ณธ ํ ์คํธ๋ฅผ ํฌํจํ HTML ์์ | |
html_content = """ | |
<div style='padding: 20px; background: #ffffff;'> | |
<div style='margin-bottom: 30px;'> | |
<textarea id='intro_text' rows='4' style=' | |
width: 100%; | |
padding: 15px; | |
border: 1px solid #ddd; | |
border-radius: 10px; | |
font-size: 1.1em; | |
line-height: 1.5; | |
resize: vertical; | |
background: #f8f9fa; | |
'>์๋ ํ์ธ์. ๋งค์ผ ๊ธ๋ก๋ฒ ์ต์ AI ์ธ๊ธฐ ํธ๋ ๋ ์๋น์ค๋ฅผ ์์๋ณด๋ '๋ฐ์ผ๋ฆฌ AI ํธ๋ ๋ฉ' ๋ด์ค์ ๋๋ค. ์ค๋์ ํ๊น ํ์ด์ค ์ธ๊ธฐ ์์ 1์๋ถํฐ 24์๊น์ง, ๋ถ์๊ณผ ํต์ฌ ๋ด์ฉ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.</textarea> | |
</div> | |
<style> | |
.script-card { | |
background: white !important; | |
border-radius: 10px; | |
padding: 20px; | |
margin-bottom: 20px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
border: 1px solid #e0e0e0; | |
} | |
.script-content { | |
color: #444 !important; | |
font-size: 1.1em; | |
line-height: 1.6; | |
white-space: pre-line; | |
} | |
</style> | |
""" | |
for idx, space in enumerate(spaces): | |
progress((idx + 1) / 24, desc=f"๋ถ์ ์ค... {idx+1}/24") | |
try: | |
source = get_space_source(space['id']) | |
source_code = source["app.py"] or source["index.html"] | |
# ์คํ์ด์ค ID์์ ์ฌ์ฉ์๋ช ์ ๊ฑฐํ๊ณ ํ๋ก์ ํธ๋ช ๋ง ์ถ์ถ | |
project_name = space['id'].split('/')[-1] | |
prompt = f""" | |
๋ค์ HuggingFace ์คํ์ด์ค๋ฅผ ์ ํ๋ธ ๋ด์ค ๋ฆฌํฌํธ ํ์์ผ๋ก ์ค๋ช ํด์ฃผ์ธ์. | |
์์์ ๋ฐ๋์ "์ค๋์ ์ธ๊ธฐ์์ {idx + 1}์์ธ {project_name}์ ๋๋ค."๋ก ์์ํ๊ณ , | |
์ด์ด์ ์ฃผ์ ๊ธฐ๋ฅ, ํน์ง, ํ์ฉ๋ฐฉ์์ 2-3๋ฌธ์ฅ์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ์ค๋ช ํด์ฃผ์ธ์. | |
์ ์ฒด ๊ธธ์ด๋ 3-4๋ฌธ์ฅ์ผ๋ก ์ ํํ๊ณ , ์ค๋ช ์ ๋ด์ค ๋ฆฌํฌํฐ์ฒ๋ผ ๋ช ํํ๊ณ ์ ๋ฌธ์ ์ผ๋ก ํด์ฃผ์ธ์. | |
์์ค์ฝ๋: | |
``` | |
{source_code[:1500]} | |
``` | |
""" | |
messages = [ | |
{"role": "system", "content": "AI ๊ธฐ์ ์ ๋ฌธ ๋ด์ค ๋ฆฌํฌํฐ์ ๋๋ค."}, | |
{"role": "user", "content": prompt} | |
] | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=2000, | |
temperature=0.7 | |
) | |
script = response.choices[0].message.content.strip() | |
html_content += f""" | |
<div class='script-card'> | |
<div class='script-content'>{script}</div> | |
</div> | |
""" | |
except Exception as e: | |
print(f"Error analyzing space {space['id']}: {e}") | |
html_content += f""" | |
<div class='script-card'> | |
<div class='script-content' style='color: red !important;'> | |
์์ {idx + 1}์ ๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. | |
</div> | |
</div> | |
""" | |
html_content += "</div>" | |
return html_content, f"24๊ฐ ์คํ์ด์ค ๋ถ์ ์๋ฃ" | |
except Exception as e: | |
error_msg = f"Error: {str(e)}" | |
return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", error_msg | |
def analyze_single_space(space: dict, source_code: str) -> str: | |
"""๋จ์ผ ์คํ์ด์ค ๋ถ์""" | |
try: | |
if not source_code: | |
return "์์ค์ฝ๋๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค." | |
prompt = f""" | |
๋ค์ ์คํ์ด์ค์ ์์ค์ฝ๋๋ฅผ ๋ถ์ํด์ฃผ์ธ์: | |
``` | |
{source_code[:4000]} | |
``` | |
๋ค์ ํญ๋ชฉ์ ๊ฐ๊ฐ ํ ์ค๋ก ์์ฝํด์ฃผ์ธ์: | |
1. ๊ฐ์: | |
2. ์์ฝ: | |
3. ํน์ง ๋ฐ ์ฅ์ : | |
4. ์ฌ์ฉ ๋์: | |
5. ์ฌ์ฉ ๋ฐฉ๋ฒ: | |
6. ์ ์ฌ ์๋น์ค์์ ์ฐจ๋ณ์ : | |
""" | |
messages = [ | |
{"role": "system", "content": "์์ค์ฝ๋ ๋ถ์ ์ ๋ฌธ๊ฐ์ ๋๋ค."}, | |
{"role": "user", "content": prompt} | |
] | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=3900, | |
temperature=0.3 | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
return f"๋ถ์ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}" | |
def create_editable_space_analysis(progress=gr.Progress()) -> List[str]: | |
"""24๊ฐ ์คํ์ด์ค ๋ถ์ ํ ์คํธ ์์ฑ""" | |
try: | |
progress(0, desc="์คํ์ด์ค ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ์ค...") | |
url = "https://huggingface.co/api/spaces" | |
response = requests.get(url, params={'full': 'true', 'limit': 24}) | |
response.raise_for_status() | |
spaces = response.json()[:24] | |
analysis_texts = [] | |
for idx, space in enumerate(spaces): | |
progress((idx + 1) / 24, desc=f"๋ถ์ ์ค... {idx+1}/24") | |
try: | |
source = get_space_source(space['id']) | |
source_code = source["app.py"] or source["index.html"] | |
# ํ๋ก์ ํธ๋ช ๋ง ์ถ์ถ | |
project_name = space['id'].split('/')[-1] | |
prompt = f""" | |
๋ค์ HuggingFace ์คํ์ด์ค๋ฅผ ๋ถ์ํ์ฌ ๋ด์ค ๋ฆฌํฌํธ ํ์์ผ๋ก ์ค๋ช ํด์ฃผ์ธ์: | |
์์์ ๋ฐ๋์ "์ค๋์ ์ธ๊ธฐ์์ {idx + 1}์์ธ {project_name}์ ๋๋ค."๋ก ์์ํ๊ณ , | |
์ด์ด์ ์ฃผ์ ๊ธฐ๋ฅ, ํน์ง, ํ์ฉ๋ฐฉ์์ ์์ฐ์ค๋ฝ๊ฒ ์ค๋ช ํด์ฃผ์ธ์. | |
์์ค์ฝ๋: | |
``` | |
{source_code[:1500]} | |
``` | |
""" | |
messages = [ | |
{"role": "system", "content": "AI ๊ธฐ์ ์ ๋ฌธ ๋ด์ค ๋ฆฌํฌํฐ์ ๋๋ค."}, | |
{"role": "user", "content": prompt} | |
] | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=2000, | |
temperature=0.7 | |
) | |
analysis_texts.append(response.choices[0].message.content.strip()) | |
except Exception as e: | |
analysis_texts.append(f"์ค๋์ ์ธ๊ธฐ์์ {idx + 1}์์ธ {project_name}์ ๋๋ค.") | |
return analysis_texts | |
except Exception as e: | |
return [f"์์ {i+1}์ ๋ถ์์ ์ค๋น์ค์ ๋๋ค." for i in range(24)] | |
def generate_video(intro_text, analysis_html): | |
"""๋น๋์ค ์์ฑ""" | |
try: | |
# HTML์์ ํ ์คํธ ์ถ์ถ | |
soup = BeautifulSoup(analysis_html, 'html.parser') | |
texts = [intro_text] + [p.text for p in soup.find_all('p')] | |
temp_dir = tempfile.mkdtemp() | |
clips = [] | |
# ๊ฐ ํ ์คํธ์ ๋ํ ํด๋ฆฝ ์์ฑ | |
for idx, text in enumerate(texts): | |
if not text or len(text.strip()) == 0: | |
continue | |
# ์ด๋ฏธ์ง ์์ฑ | |
img = Image.new('RGB', (800, 600), (0, 0, 0)) | |
draw = ImageDraw.Draw(img) | |
# ํ ์คํธ๋ฅผ ์ฌ๋ฌ ์ค๋ก ๋๋์ด ๊ทธ๋ฆฌ๊ธฐ | |
lines = textwrap.wrap(text, width=40) | |
y = 40 | |
for line in lines: | |
draw.text((40, y), line, fill=(255, 255, 255)) | |
y += 30 | |
# ์์ฑ ์์ฑ | |
tts = gTTS(text=text, lang='ko', slow=False) | |
audio_path = os.path.join(temp_dir, f"audio_{idx}.mp3") | |
tts.save(audio_path) | |
audio_clip = AudioFileClip(audio_path) | |
# ํด๋ฆฝ ์์ฑ | |
video_clip = ImageClip(np.array(img)) | |
video_clip = video_clip.set_duration(audio_clip.duration) | |
video_clip = video_clip.set_audio(audio_clip) | |
clips.append(video_clip) | |
# ์ต์ข ์์ ์์ฑ | |
final_clip = concatenate_videoclips(clips) | |
output_path = "output_video.mp4" | |
final_clip.write_videofile( | |
output_path, | |
fps=24, | |
codec='libx264', | |
audio_codec='aac' | |
) | |
return output_path | |
except Exception as e: | |
print(f"Video generation error: {e}") | |
traceback.print_exc() | |
return None | |
def create_interface(): | |
with gr.Blocks(title="HuggingFace Trending Board", css=""" | |
.search-sort-container { | |
background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(240,240,255,0.95)); | |
border-radius: 15px; | |
padding: 20px; | |
margin: 10px 0; | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
overflow: visible; | |
} | |
.search-box { | |
border: 2px solid #e1e1e1; | |
border-radius: 10px; | |
padding: 12px; | |
transition: all 0.3s ease; | |
background: linear-gradient(135deg, #ffffff, #f8f9ff); | |
width: 100%; | |
} | |
.search-box:focus { | |
border-color: #7b61ff; | |
box-shadow: 0 0 0 2px rgba(123,97,255,0.2); | |
background: linear-gradient(135deg, #ffffff, #f0f3ff); | |
} | |
.refresh-btn { | |
background: linear-gradient(135deg, #7b61ff, #6366f1); | |
color: white; | |
border: none; | |
padding: 10px 20px; | |
border-radius: 10px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
width: 120px; | |
height: 80px !important; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin-left: auto; | |
font-size: 1.2em !important; | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
} | |
.refresh-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 6px 12px rgba(0,0,0,0.2); | |
background: linear-gradient(135deg, #8b71ff, #7376f1); | |
} | |
""") as interface: | |
gr.Markdown(""" | |
# ๐ค HuggingFace Trending 24 NEWS | |
<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;'> | |
HuggingFace Trending Spaces Top 24 NEWS | |
</div> | |
""") | |
with gr.Tabs() as tabs: | |
# Spaces ํญ | |
with gr.Tab("๐ฏ Trending Spaces"): | |
with gr.Row(elem_classes="search-sort-container"): | |
with gr.Column(scale=2): | |
spaces_search = gr.Textbox( | |
label="๐ Search Spaces", | |
placeholder="Enter keywords to search...", | |
elem_classes="search-box" | |
) | |
with gr.Column(scale=2): | |
spaces_sort = gr.Radio( | |
choices=["rank", "rising_rate", "popularity"], | |
value="rank", | |
label="Sort by", | |
interactive=True | |
) | |
with gr.Column(scale=1): | |
spaces_refresh_btn = gr.Button( | |
"๐ Refresh", | |
variant="primary", | |
elem_classes="refresh-btn" | |
) | |
spaces_gallery = gr.HTML() | |
spaces_status = gr.Markdown("Loading...") | |
# Models ํญ | |
with gr.Tab("๐ค Trending Models"): | |
with gr.Row(elem_classes="search-sort-container"): | |
with gr.Column(scale=2): | |
models_search = gr.Textbox( | |
label="๐ Search Models", | |
placeholder="Enter keywords to search...", | |
elem_classes="search-box" | |
) | |
with gr.Column(scale=2): | |
models_sort = gr.Radio( | |
choices=["rank", "rising_rate", "popularity"], | |
value="rank", | |
label="Sort by", | |
interactive=True | |
) | |
with gr.Column(scale=1): | |
models_refresh_btn = gr.Button( | |
"๐ Refresh", | |
variant="primary", | |
elem_classes="refresh-btn" | |
) | |
models_gallery = gr.HTML() | |
models_status = gr.Markdown("Loading...") | |
# Datasets ํญ | |
with gr.Tab("๐ Trending Datasets"): | |
with gr.Row(elem_classes="search-sort-container"): | |
with gr.Column(scale=2): | |
datasets_search = gr.Textbox( | |
label="๐ Search Datasets", | |
placeholder="Enter keywords to search...", | |
elem_classes="search-box" | |
) | |
with gr.Column(scale=2): | |
datasets_sort = gr.Radio( | |
choices=["rank", "rising_rate", "popularity"], | |
value="rank", | |
label="Sort by", | |
interactive=True | |
) | |
with gr.Column(scale=1): | |
datasets_refresh_btn = gr.Button( | |
"๐ Refresh", | |
variant="primary", | |
elem_classes="refresh-btn" | |
) | |
datasets_gallery = gr.HTML() | |
datasets_status = gr.Markdown("Loading...") | |
with gr.Tab("๐ Top 24 Spaces Analysis"): | |
with gr.Row(): | |
analysis_refresh_btn = gr.Button( | |
"๐ Analyze All 24 Spaces", | |
variant="primary" | |
) | |
# ์ธํธ๋ก ์น์ | |
with gr.Row(): | |
with gr.Column(scale=3): | |
intro_text = gr.Textbox( | |
value="์๋ ํ์ธ์. ๋งค์ผ ๊ธ๋ก๋ฒ ์ต์ AI ์ธ๊ธฐ ํธ๋ ๋ ์๋น์ค๋ฅผ ์์๋ณด๋ '๋ฐ์ผ๋ฆฌ AI ํธ๋ ๋ฉ' ๋ด์ค์ ๋๋ค.", | |
label="์ธํธ๋ก ํ ์คํธ", | |
lines=4 | |
) | |
# ๋ถ์ ๊ฒฐ๊ณผ ์ปจํ ์ด๋๋ค | |
analysis_boxes = [] | |
space_images = [] | |
# Analysis ํญ์์ ์ด๋ฏธ์ง ์ปดํฌ๋ํธ ์์ฑ ๋ถ๋ถ ์์ | |
for i in range(24): | |
with gr.Row(): | |
with gr.Column(scale=3): | |
text_box = gr.Textbox( | |
label=f"Space #{i+1}", | |
lines=3, | |
interactive=True | |
) | |
analysis_boxes.append(text_box) | |
with gr.Column(scale=1): | |
img = gr.Image( | |
label=f"Screenshot #{i+1}", | |
type="filepath", | |
interactive=True, # ์ด๋ฏธ์ง ๊ต์ฒด ๊ฐ๋ฅํ๋๋ก ์ค์ | |
height=200, # ์ด๋ฏธ์ง ๋์ด ์ค์ | |
sources=["upload", "clipboard"] # ์ ๋ก๋์ ํด๋ฆฝ๋ณด๋ ๋ถ์ฌ๋ฃ๊ธฐ ํ์ฉ | |
) | |
space_images.append(img) | |
# ๋น๋์ค ์์ฑ ์น์ | |
with gr.Row(): | |
generate_btn = gr.Button( | |
"๐ฌ Generate Video", | |
variant="primary" | |
) | |
video_output = gr.Video(label="Generated Video") | |
# ์ด๋ฏธ์ง ์ ๋ฐ์ดํธ ๋ฒํผ | |
with gr.Row(): | |
update_images_btn = gr.Button( | |
"๐ Update Screenshots", | |
variant="secondary" | |
) | |
# ์ด๋ฏธ์ง ๊ต์ฒด ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ถ๊ฐ | |
for img in space_images: | |
img.change( | |
fn=lambda x: x, # ๊ฐ๋จํ ํจ์ค์ค๋ฃจ ํจ์ | |
inputs=[img], | |
outputs=[img] | |
) | |
# Event handlers | |
spaces_refresh_btn.click( | |
fn=get_trending_spaces, | |
inputs=[spaces_search, spaces_sort], | |
outputs=[spaces_gallery, spaces_status] | |
) | |
models_refresh_btn.click( | |
fn=get_models, | |
inputs=[models_search, models_sort], | |
outputs=[models_gallery, models_status] | |
) | |
datasets_refresh_btn.click( | |
fn=get_datasets, | |
inputs=[datasets_search, datasets_sort], | |
outputs=[datasets_gallery, datasets_status] | |
) | |
# Analysis ํญ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ค | |
analysis_refresh_btn.click( | |
fn=on_analyze, | |
outputs=analysis_boxes + space_images | |
) | |
generate_btn.click( | |
fn=on_generate_video, | |
inputs=[intro_text] + analysis_boxes, | |
outputs=video_output | |
) | |
update_images_btn.click( | |
fn=update_screenshots, | |
outputs=space_images | |
) | |
# ๊ฒ์์ด ๋ณ๊ฒฝ ์ ์๋ ์๋ก๊ณ ์นจ | |
spaces_search.change( | |
fn=get_trending_spaces, | |
inputs=[spaces_search, spaces_sort], | |
outputs=[spaces_gallery, spaces_status] | |
) | |
models_search.change( | |
fn=get_models, | |
inputs=[models_search, models_sort], | |
outputs=[models_gallery, models_status] | |
) | |
datasets_search.change( | |
fn=get_datasets, | |
inputs=[datasets_search, datasets_sort], | |
outputs=[datasets_gallery, datasets_status] | |
) | |
# ์ ๋ ฌ ๋ฐฉ์ ๋ณ๊ฒฝ ์ ์๋ ์๋ก๊ณ ์นจ | |
spaces_sort.change( | |
fn=get_trending_spaces, | |
inputs=[spaces_search, spaces_sort], | |
outputs=[spaces_gallery, spaces_status] | |
) | |
models_sort.change( | |
fn=get_models, | |
inputs=[models_search, models_sort], | |
outputs=[models_gallery, models_status] | |
) | |
datasets_sort.change( | |
fn=get_datasets, | |
inputs=[datasets_search, datasets_sort], | |
outputs=[datasets_gallery, datasets_status] | |
) | |
# ์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋ | |
interface.load( | |
fn=get_trending_spaces, | |
inputs=[spaces_search, spaces_sort], | |
outputs=[spaces_gallery, spaces_status] | |
) | |
interface.load( | |
fn=get_models, | |
inputs=[models_search, models_sort], | |
outputs=[models_gallery, models_status] | |
) | |
interface.load( | |
fn=get_datasets, | |
inputs=[datasets_search, datasets_sort], | |
outputs=[datasets_gallery, datasets_status] | |
) | |
return interface | |
def on_analyze(progress=gr.Progress()): | |
"""๋ถ์ ์คํ ๋ฐ ํ ์คํธ๋ฐ์ค/์ด๋ฏธ์ง ์ ๋ฐ์ดํธ""" | |
try: | |
url = "https://huggingface.co/api/spaces" | |
response = requests.get(url, params={'full': 'true', 'limit': 24}) | |
response.raise_for_status() | |
spaces = response.json()[:24] | |
text_results = [] | |
image_results = [] | |
temp_dir = Path("temp_screenshots") | |
temp_dir.mkdir(exist_ok=True) | |
for idx, space in enumerate(spaces): | |
progress((idx + 1) / 24, desc=f"๋ถ์ ์ค... {idx+1}/24") | |
try: | |
# ์คํฌ๋ฆฐ์ท ์ฒ๋ฆฌ | |
space_url = f"https://huggingface.co/spaces/{space['id']}" | |
screenshot_path = temp_dir / f"space_{idx:03d}.jpg" | |
# ์ด๋ฏธ์ง ์ ์ฅ | |
screenshot_base64 = get_cached_screenshot(space_url) | |
if screenshot_base64: | |
try: | |
img_data = base64.b64decode(screenshot_base64) | |
with open(screenshot_path, 'wb') as f: | |
f.write(img_data) | |
image_results.append(str(screenshot_path)) | |
except Exception as e: | |
print(f"Screenshot save error: {e}") | |
image_results.append(None) | |
else: | |
image_results.append(None) | |
# ์์ค์ฝ๋ ๊ฐ์ ธ์ค๊ธฐ | |
source = get_space_source(space['id']) | |
source_code = "" | |
# ์์ค์ฝ๋ ์ฐ์ ์์ ์ค์ | |
if source.get("app.py"): | |
source_code = source["app.py"] | |
elif source.get("streamlit_app.py"): | |
source_code = source["streamlit_app.py"] | |
elif source.get("gradio_ui.py"): | |
source_code = source["gradio_ui.py"] | |
elif source.get("main.py"): | |
source_code = source["main.py"] | |
elif source.get("index.html"): | |
source_code = source["index.html"] | |
if not source_code.strip(): | |
text_results.append(f"์ค๋์ ์ธ๊ธฐ์์ {idx + 1}์ ์คํ์ด์ค์ ๋๋ค. ์์ค์ฝ๋๋ฅผ ํ์ธํ ์ ์์ด ์์ธํ ๋ถ์์ ์ด๋ ต์ต๋๋ค.") | |
continue | |
# ํ ์คํธ ๋ถ์ | |
project_name = space['id'].split('/')[-1] | |
# ์์ค์ฝ๋ ๋ถ์์ ์ํ ํ๋กฌํํธ | |
prompt = f""" | |
๋ค์ HuggingFace ์คํ์ด์ค์ ์์ค์ฝ๋๋ฅผ ์์ธํ ๋ถ์ํด์ฃผ์ธ์: | |
์คํ์ด์ค ์ ๋ณด: | |
- ์ด๋ฆ: {project_name} | |
- ์์: {idx + 1}์ | |
- URL: {space_url} | |
์์ค์ฝ๋: | |
```python | |
{source_code[:2000]} | |
``` | |
๋ค์ ๋ด์ฉ์ ๋ถ์ํ์ฌ ์ค๋ช ํด์ฃผ์ธ์: | |
1. ์ด ์คํ์ด์ค๊ฐ ์ฌ์ฉํ๋ ์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ๋ ์์ํฌ | |
2. ๊ตฌํ๋ ํต์ฌ ๊ธฐ๋ฅ๊ณผ ์๋ ๋ฐฉ์ | |
3. ๊ธฐ์ ์ ํน์ง๊ณผ ์ฅ์ | |
์๋ต ํ์: | |
- ๋ด์ค ๋ฆฌํฌํฐ์ฒ๋ผ ์์ฐ์ค๋ฝ๊ฒ ์ค๋ช | |
- ๊ธฐ์ ์ ๋ด์ฉ์ ํฌํจํ๋ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช | |
- ์ค์ ์์ค์ฝ๋์์ ํ์ธ๋ ๋ด์ฉ๋ง ํฌํจ | |
-'์ธ๋ ํ์ธ์'์ ๊ฐ์ ์ธ์ฌ์ '์๊ธฐ์๊ฐ'๋ ํฌํจํ์ง๋ง์๋ผ. | |
- ์ต๋ 7๋ฌธ์ฅ ์ด๋ด๋ก ๊ฐ๋จ๋ช ๋ฃํ๊ฒ ์์ฑ(๋ฐ๋์ ์์ฑ๋ ๋ฌธ์ฅ์ด ์๋๊ฒฝ์ฐ ์ถ๋ ฅํ์ง๋ง๊ฒ) | |
""" | |
messages = [ | |
{ | |
"role": "system", | |
"content": "์ ๋ ์์ค์ฝ๋๋ฅผ ๋ถ์ํ์ฌ ๊ธฐ์ ์ ๋ด์ฉ์ 10์ธ ์๋๋ ์ดํดํ ์ ์๊ฒ ์ฝ๊ฒ ์ค๋ช ํ๋ AI ๊ธฐ์ ์ ๋ฌธ ๋ฆฌํฌํฐ์ ๋๋ค." | |
}, | |
{"role": "user", "content": prompt} | |
] | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=1000, | |
temperature=0.3 | |
) | |
analysis = response.choices[0].message.content.strip() | |
# ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ์ค์ ์์ค์ฝ๋ ๊ธฐ๋ฐ์ธ์ง ํ์ธ | |
if "๋ผ์ด๋ธ๋ฌ๋ฆฌ" in analysis or "ํ๋ ์์ํฌ" in analysis or "ํจ์" in analysis: | |
text_results.append(analysis) | |
else: | |
# ์ฌ์๋ | |
prompt += "\n\n์ฃผ์: ๋ฐ๋์ ์์ค์ฝ๋์์ ํ์ธ๋ ์ค์ ๊ธฐ์ ์ ๋ด์ฉ๋ง ํฌํจํ์ฌ ์ค๋ช ํด์ฃผ์ธ์." | |
messages[1]["content"] = prompt | |
response = hf_client.chat_completion( | |
messages, | |
max_tokens=300, | |
temperature=0.2 | |
) | |
text_results.append(response.choices[0].message.content.strip()) | |
except Exception as e: | |
print(f"Error processing space {space['id']}: {e}") | |
text_results.append(f"ํ์ฌ {idx + 1}์ ์คํ์ด์ค์ ๋ํ ๋ถ์์ ์ค๋น์ค์ ๋๋ค.") | |
if len(image_results) <= idx: | |
image_results.append(None) | |
# ๊ฒฐ๊ณผ ๊ฐ์ ๋ง์ถ๊ธฐ | |
while len(text_results) < 24: | |
text_results.append("๋ถ์์ ์ค๋น์ค์ ๋๋ค.") | |
while len(image_results) < 24: | |
image_results.append(None) | |
return text_results + image_results | |
except Exception as e: | |
print(f"Analysis error: {e}") | |
return ["๋ถ์์ ์ค๋น์ค์ ๋๋ค."] * 24 + [None] * 24 | |
def on_generate_video(intro_text, *texts): | |
"""์์ ์์ฑ""" | |
try: | |
temp_dir = tempfile.mkdtemp() | |
clips = [] | |
# ์ธํธ๋ก ์ฒ๋ฆฌ - intro.png ์ฌ์ฉ | |
try: | |
intro_img = Image.open("intro.png") | |
# ํฌ๊ธฐ๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ ๋ฆฌ์ฌ์ด์ฆ | |
if intro_img.size != (800, 600): | |
intro_img = intro_img.resize((800, 600), Image.Resampling.LANCZOS) | |
except Exception as e: | |
print(f"Error loading intro image: {e}") | |
# intro.png๊ฐ ์๋ ๊ฒฝ์ฐ ๊ฒ์ ๋ฐฐ๊ฒฝ์ ํ ์คํธ | |
intro_img = Image.new('RGB', (800, 600), (0, 0, 0)) | |
draw = ImageDraw.Draw(intro_img) | |
try: | |
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) | |
except: | |
font = ImageFont.load_default() | |
lines = textwrap.wrap(intro_text, width=40) | |
y = 40 | |
for line in lines: | |
draw.text((40, y), line, fill=(255, 255, 255), font=font) | |
y += 30 | |
# ์ธํธ๋ก ํด๋ฆฝ ์์ฑ | |
intro_audio = gTTS(text=intro_text, lang='ko', slow=False) | |
intro_audio_path = os.path.join(temp_dir, "intro.mp3") | |
intro_audio.save(intro_audio_path) | |
intro_audio_clip = AudioFileClip(intro_audio_path) | |
intro_clip = ImageClip(np.array(intro_img)) | |
intro_clip = intro_clip.set_duration(intro_audio_clip.duration) | |
intro_clip = intro_clip.set_audio(intro_audio_clip) | |
clips.append(intro_clip) | |
# ๊ฐ ์คํ์ด์ค ์ฒ๋ฆฌ | |
for idx, text in enumerate(texts): | |
if not text or len(str(text).strip()) == 0: | |
continue | |
# ์คํฌ๋ฆฐ์ท ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๊ธฐ | |
screenshot_path = f"temp_screenshots/space_{idx:03d}.jpg" | |
if os.path.exists(screenshot_path): | |
img = Image.open(screenshot_path) | |
# ์ด๋ฏธ์ง ํฌ๊ธฐ ์กฐ์ | |
img = img.resize((800, 600), Image.Resampling.LANCZOS) | |
else: | |
# ์คํฌ๋ฆฐ์ท์ด ์๋ ๊ฒฝ์ฐ ๊ฒ์ ๋ฐฐ๊ฒฝ | |
img = Image.new('RGB', (800, 600), (0, 0, 0)) | |
# ์์ฑ ์์ฑ | |
tts = gTTS(text=str(text), lang='ko', slow=False) | |
audio_path = os.path.join(temp_dir, f"audio_{idx}.mp3") | |
tts.save(audio_path) | |
audio_clip = AudioFileClip(audio_path) | |
# ํด๋ฆฝ ์์ฑ (์ด๋ฏธ์ง ์ฌ์ฉ) | |
video_clip = ImageClip(np.array(img)) | |
video_clip = video_clip.set_duration(audio_clip.duration) | |
video_clip = video_clip.set_audio(audio_clip) | |
clips.append(video_clip) | |
# ์ต์ข ์์ ์์ฑ | |
final_clip = concatenate_videoclips(clips) | |
output_path = "output_video.mp4" | |
final_clip.write_videofile( | |
output_path, | |
fps=24, | |
codec='libx264', | |
audio_codec='aac' | |
) | |
return output_path | |
except Exception as e: | |
print(f"Video generation error: {e}") | |
traceback.print_exc() | |
return None | |
finally: | |
try: | |
if 'temp_dir' in locals(): | |
shutil.rmtree(temp_dir) | |
except Exception as e: | |
print(f"Cleanup error: {e}") | |
def update_screenshots(): | |
"""์คํฌ๋ฆฐ์ท ์ผ๊ด ์ ๋ฐ์ดํธ""" | |
try: | |
url = "https://huggingface.co/api/spaces" | |
response = requests.get(url, params={'full': 'true', 'limit': 24}) | |
spaces = response.json()[:24] | |
image_paths = [] | |
temp_dir = Path("temp_screenshots") | |
temp_dir.mkdir(exist_ok=True) | |
for idx, space in enumerate(spaces): | |
try: | |
space_url = f"https://huggingface.co/spaces/{space['id']}" | |
time.sleep(5) # ์ถฉ๋ถํ ๋ก๋ฉ ์๊ฐ | |
screenshot_base64 = get_cached_screenshot(space_url) | |
screenshot_path = temp_dir / f"space_{idx:03d}.jpg" | |
if screenshot_base64: | |
try: | |
img_data = base64.b64decode(screenshot_base64) | |
# ์ด๋ฏธ์ง ์ ์ฅ ๋ฐ ์ต์ ํ | |
with open(screenshot_path, 'wb') as f: | |
f.write(img_data) | |
# ์ด๋ฏธ์ง ํฌ๊ธฐ ์ต์ ํ | |
with Image.open(screenshot_path) as img: | |
img = img.resize((800, 600), Image.Resampling.LANCZOS) | |
img.save(screenshot_path, format="JPEG", quality=85, optimize=True) | |
image_paths.append(str(screenshot_path)) | |
except Exception as e: | |
print(f"Screenshot save error: {e}") | |
image_paths.append(None) | |
else: | |
image_paths.append(None) | |
except Exception as e: | |
print(f"Error capturing screenshot for space {idx+1}: {e}") | |
image_paths.append(None) | |
return image_paths | |
except Exception as e: | |
print(f"Update screenshots error: {e}") | |
return [None] * 24 | |
if __name__ == "__main__": | |
try: | |
CACHE_DIR.mkdir(exist_ok=True) | |
cleanup_cache() | |
demo = create_interface() | |
demo.launch( | |
share=True, | |
inbrowser=True, | |
show_api=False, | |
max_threads=4 | |
) | |
except Exception as e: | |
print(f"Application error: {e}") |