openfree's picture
Update app.py
020670e verified
raw
history blame
27.4 kB
import requests
import gradio as gr
from datetime import datetime
import random
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from PIL import Image
from io import BytesIO
import base64
def take_screenshot(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)
driver.implicitly_wait(10)
screenshot = driver.get_screenshot_as_png()
img = Image.open(BytesIO(screenshot))
buffered = BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode()
except WebDriverException as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ ์‹คํŒจ: {str(e)}")
return None
finally:
if 'driver' in locals():
driver.quit()
USERNAME = "openfree"
def format_timestamp(timestamp):
if not timestamp:
return 'N/A'
try:
# ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ
if isinstance(timestamp, str):
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
# ์ •์ˆ˜(๋ฐ€๋ฆฌ์ดˆ)์ธ ๊ฒฝ์šฐ
elif isinstance(timestamp, (int, float)):
dt = datetime.fromtimestamp(timestamp / 1000) # ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ์ดˆ๋กœ ๋ณ€ํ™˜
else:
return 'N/A'
return dt.strftime('%Y-%m-%d %H:%M')
except Exception as e:
print(f"Timestamp conversion error: {str(e)} for timestamp: {timestamp}")
return 'N/A'
def should_exclude_space(space_name):
"""ํŠน์ • ์ŠคํŽ˜์ด์Šค๋ฅผ ์ œ์™ธํ•˜๋Š” ํ•„ํ„ฐ ํ•จ์ˆ˜"""
exclude_keywords = [
'mixgen3', 'ginid', 'mouse', 'flxtrainlora',
'vidslicegpu', 'stickimg', 'ultpixgen', 'SORA',
'badassgi', 'newsplus', 'chargen', 'news',
'testhtml'
]
return any(keyword.lower() in space_name.lower() for keyword in exclude_keywords)
def get_pastel_color(index):
"""Generate unique pastel colors based on index"""
pastel_colors = [
'#FFE6E6', # ์—ฐํ•œ ๋ถ„ํ™
'#FFE6FF', # ์—ฐํ•œ ๋ณด๋ผ
'#E6E6FF', # ์—ฐํ•œ ํŒŒ๋ž‘
'#E6FFFF', # ์—ฐํ•œ ํ•˜๋Š˜
'#E6FFE6', # ์—ฐํ•œ ์ดˆ๋ก
'#FFFFE6', # ์—ฐํ•œ ๋…ธ๋ž‘
'#FFF0E6', # ์—ฐํ•œ ์ฃผํ™ฉ
'#F0E6FF', # ์—ฐํ•œ ๋ผ๋ฒค๋”
'#FFE6F0', # ์—ฐํ•œ ๋กœ์ฆˆ
'#E6FFF0', # ์—ฐํ•œ ๋ฏผํŠธ
'#F0FFE6', # ์—ฐํ•œ ๋ผ์ž„
'#FFE6EB', # ์—ฐํ•œ ์ฝ”๋ž„
'#E6EBFF', # ์—ฐํ•œ ํผํ”Œ๋ธ”๋ฃจ
'#FFE6F5', # ์—ฐํ•œ ํ•‘ํฌ
'#E6FFF5', # ์—ฐํ•œ ํ„ฐ์ฝ”์ด์ฆˆ
'#F5E6FF', # ์—ฐํ•œ ๋ชจ๋ธŒ
'#FFE6EC', # ์—ฐํ•œ ์‚ด๋ชฌ
'#E6FFEC', # ์—ฐํ•œ ์Šคํ”„๋ง๊ทธ๋ฆฐ
'#ECE6FF', # ์—ฐํ•œ ํŽ˜๋ฆฌ์œ™ํด
'#FFE6F7', # ์—ฐํ•œ ๋งค๊ทธ๋†€๋ฆฌ์•„
]
return pastel_colors[index % len(pastel_colors)]
def get_space_card(space, index):
"""Generate HTML card for a space with colorful design and lots of emojis"""
space_id = space.get('id', '')
space_name = space_id.split('/')[-1]
likes = space.get('likes', 0)
created_at = format_timestamp(space.get('createdAt'))
sdk = space.get('sdk', 'N/A')
# SDK๋ณ„ ์ด๋ชจ์ง€ ๋ฐ ๊ด€๋ จ ์ด๋ชจ์ง€ ์„ธํŠธ
sdk_emoji_sets = {
'gradio': {
'main': '๐ŸŽจ',
'related': ['๐Ÿ–ผ๏ธ', '๐ŸŽญ', '๐ŸŽช', '๐ŸŽ ', '๐ŸŽก', '๐ŸŽข', '๐ŸŽฏ', '๐ŸŽฒ', '๐ŸŽฐ', '๐ŸŽณ']
},
'streamlit': {
'main': 'โšก',
'related': ['๐Ÿ’ซ', 'โœจ', 'โญ', '๐ŸŒŸ', '๐Ÿ’ฅ', 'โšก', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽ†', '๐ŸŽ‡']
},
'docker': {
'main': '๐Ÿณ',
'related': ['๐Ÿ‹', '๐ŸŒŠ', '๐ŸŒ', '๐Ÿšข', 'โ›ด๏ธ', '๐Ÿ›ฅ๏ธ', '๐Ÿ ', '๐Ÿก', '๐Ÿฆˆ', '๐Ÿฌ']
},
'static': {
'main': '๐Ÿ“„',
'related': ['๐Ÿ“', '๐Ÿ“ฐ', '๐Ÿ“‘', '๐Ÿ—‚๏ธ', '๐Ÿ“', '๐Ÿ“‚', '๐Ÿ“š', '๐Ÿ“–', '๐Ÿ“’', '๐Ÿ“”']
},
'panel': {
'main': '๐Ÿ“Š',
'related': ['๐Ÿ“ˆ', '๐Ÿ“‰', '๐Ÿ’น', '๐Ÿ“‹', '๐Ÿ“Œ', '๐Ÿ“', '๐Ÿ—บ๏ธ', '๐ŸŽฏ', '๐Ÿ“', '๐Ÿ“']
},
'N/A': {
'main': '๐Ÿ”ง',
'related': ['๐Ÿ”จ', 'โš’๏ธ', '๐Ÿ› ๏ธ', 'โš™๏ธ', '๐Ÿ”ฉ', 'โ›๏ธ', 'โšก', '๐Ÿ”Œ', '๐Ÿ’ก', '๐Ÿ”‹']
}
}
# SDK์— ๋”ฐ๋ฅธ ์ด๋ชจ์ง€ ์„ ํƒ
sdk_lower = sdk.lower()
bg_color = get_pastel_color(index) # ์ธ๋ฑ์Šค ๊ธฐ๋ฐ˜ ์ƒ‰์ƒ ์„ ํƒ
emoji_set = sdk_emoji_sets.get(sdk_lower, sdk_emoji_sets['N/A'])
main_emoji = emoji_set['main']
# ๋žœ๋คํ•˜๊ฒŒ 3๊ฐœ์˜ ๊ด€๋ จ ์ด๋ชจ์ง€ ์„ ํƒ
decorative_emojis = random.sample(emoji_set['related'], 3)
# ์ถ”๊ฐ€ ์žฅ์‹์šฉ ์ด๋ชจ์ง€
general_emojis = ['๐Ÿš€', '๐Ÿ’ซ', 'โญ', '๐ŸŒŸ', 'โœจ', '๐Ÿ’ฅ', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽฏ', '๐ŸŽจ',
'๐ŸŽญ', '๐ŸŽช', '๐ŸŽข', '๐ŸŽก', '๐ŸŽ ', '๐ŸŽช', '๐ŸŽญ', '๐ŸŽจ', '๐ŸŽฏ', '๐ŸŽฒ']
random_emojis = random.sample(general_emojis, 3)
# ์ข‹์•„์š” ์ˆ˜์— ๋”ฐ๋ฅธ ํ•˜ํŠธ ์ด๋ชจ์ง€
heart_emoji = 'โค๏ธ' if likes > 100 else '๐Ÿ’–' if likes > 50 else '๐Ÿ’' if likes > 10 else '๐Ÿค'
return f"""
<div style='border: none;
padding: 25px;
margin: 15px;
border-radius: 20px;
background-color: {bg_color};
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.3s ease-in-out;
position: relative;
overflow: hidden;'
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
<div style='position: absolute; top: -15px; right: -15px; font-size: 100px; opacity: 0.1;'>
{main_emoji}
</div>
<div style='position: absolute; top: 10px; right: 10px; font-size: 20px;'>
{decorative_emojis[0]}
</div>
<div style='position: absolute; bottom: 10px; left: 10px; font-size: 20px;'>
{decorative_emojis[1]}
</div>
<div style='position: absolute; top: 50%; right: 10px; font-size: 20px;'>
{decorative_emojis[2]}
</div>
<h3 style='color: #2d2d2d;
margin: 0 0 20px 0;
font-size: 1.4em;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
<a href='https://huggingface.co/spaces/{space_id}' target='_blank'
style='text-decoration: none; color: #2d2d2d;'>
{space_name}
</a>
<span style='font-size: 1.3em'>{random_emojis[1]}</span>
</h3>
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>SDK:</strong> {main_emoji} {sdk} {decorative_emojis[0]}
</p>
<p style='margin: 8px 0;'>
<strong>Created:</strong> ๐Ÿ“… {created_at} โฐ
</p>
<p style='margin: 8px 0;'>
<strong>Likes:</strong> {heart_emoji} {likes} {random_emojis[2]}
</p>
</div>
<div style='margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;'>
<a href='https://huggingface.co/spaces/{space_id}' target='_blank'
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
color: white;
padding: 10px 20px;
border-radius: 15px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0,132,255,0.3);'
onmouseover='this.style.transform="scale(1.05)"; this.style.boxShadow="0 4px 12px rgba(0,132,255,0.4)"'
onmouseout='this.style.transform="scale(1)"; this.style.boxShadow="0 2px 8px rgba(0,132,255,0.3)"'>
<span>View Space</span> ๐Ÿš€ {random_emojis[0]}
</a>
<span style='color: #666; font-size: 0.9em; opacity: 0.7;'>
๐Ÿ†” {space_id} {decorative_emojis[2]}
</span>
</div>
</div>
"""
def get_vercel_deployments():
"""Vercel API๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๋ฐฐํฌ๋œ ์„œ๋น„์Šค ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ ์šฉ)"""
token = "A8IFZmgW2cqA4yUNlLPnci0N"
base_url = "https://api.vercel.com/v6/deployments"
all_deployments = []
has_next = True
page = 1
until = None # ์ฒซ ์š”์ฒญ์—์„œ๋Š” until ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
try:
while has_next:
# URL ๊ตฌ์„ฑ (ํŽ˜์ด์ง€๋„ค์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ)
url = f"{base_url}?limit=100"
if until:
url += f"&until={until}"
print(f"Fetching page {page}... URL: {url}") # ๋””๋ฒ„๊น…์šฉ
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"Vercel API Error: {response.text}")
break
data = response.json()
current_deployments = data.get('deployments', [])
if not current_deployments: # ๋” ์ด์ƒ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ
break
all_deployments.extend(current_deployments)
# ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ์œ„ํ•œ until ๊ฐ’ ์„ค์ •
pagination = data.get('pagination', {})
until = pagination.get('next')
has_next = bool(until) # until ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ
print(f"Page {page} fetched. Got {len(current_deployments)} deployments") # ๋””๋ฒ„๊น…์šฉ
page += 1
print(f"Total deployments fetched: {len(all_deployments)}") # ๋””๋ฒ„๊น…์šฉ
# ์ƒํƒœ๊ฐ€ 'READY'์ด๊ณ  'url'์ด ์žˆ๋Š” ๋ฐฐํฌ๋งŒ ํ•„ํ„ฐ๋งํ•˜๊ณ  'javis1' ์ œ์™ธ
active_deployments = [
dep for dep in all_deployments
if dep.get('state') == 'READY' and
dep.get('url') and
'javis1' not in dep.get('name', '').lower()
]
print(f"Active deployments after filtering: {len(active_deployments)}") # ๋””๋ฒ„๊น…์šฉ
return active_deployments
except Exception as e:
print(f"Error fetching Vercel deployments: {str(e)}")
return []
def get_vercel_card(deployment, index, is_top_best=False):
"""Vercel ๋ฐฐํฌ ์นด๋“œ HTML ์ƒ์„ฑ ํ•จ์ˆ˜"""
raw_url = deployment.get('url', '')
# URL ์ฒ˜๋ฆฌ
if raw_url.startswith('http'):
url = raw_url
else:
url = f"https://{raw_url}"
name = deployment.get('name', '์ด๋ฆ„ ์—†๋Š” ํ”„๋กœ์ ํŠธ')
created = format_timestamp(deployment.get('created'))
state = deployment.get('state', 'N/A')
# ์นด๋“œ ID ์ƒ์„ฑ
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
# Top Best ํ•ญ๋ชฉ์ผ ๊ฒฝ์šฐ์˜ ์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ
screenshot_html = ""
if is_top_best:
try:
screenshot_base64 = take_screenshot(raw_url)
if screenshot_base64:
screenshot_html = f"""
<div style="width: 100%; height: 200px; overflow: hidden; border-radius: 10px; margin-bottom: 15px;">
<img src="data:image/png;base64,{screenshot_base64}"
style="width: 100%; height: 100%; object-fit: cover;"
alt="{name} ์Šคํฌ๋ฆฐ์ƒท"/>
</div>
"""
except Exception as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
bg_color = get_pastel_color(index + (20 if not is_top_best else 0))
tech_emojis = ['โšก', '๐Ÿš€', '๐ŸŒŸ', 'โœจ', '๐Ÿ’ซ', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽฏ', '๐ŸŽจ', '๐Ÿ”ฎ']
random_emojis = random.sample(tech_emojis, 3)
# Top Best์™€ ์ผ๋ฐ˜ ์นด๋“œ์˜ ๋‚ด์šฉ์„ ๋‹ค๋ฅด๊ฒŒ ๊ตฌ์„ฑ
if is_top_best:
info_section = f"""
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>URL:</strong> ๐Ÿ”— {url}
</p>
</div>
"""
else:
info_section = f"""
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>Status:</strong> โœ… {state}
</p>
<p style='margin: 8px 0;'>
<strong>Created:</strong> ๐Ÿ“… {created}
</p>
<p style='margin: 8px 0;'>
<strong>URL:</strong> ๐Ÿ”— {url}
</p>
</div>
"""
return f"""
<div id="{card_id}" class="vercel-card"
data-likes="0"
style='border: none;
padding: 25px;
margin: 15px;
border-radius: 20px;
background-color: {bg_color};
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.3s ease-in-out;
position: relative;
overflow: hidden;'
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
{screenshot_html}
<h3 style='color: #2d2d2d;
margin: 0 0 20px 0;
font-size: 1.4em;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
<a href='{url}' target='_blank'
style='text-decoration: none; color: #2d2d2d;'>
{name}
</a>
<span style='font-size: 1.3em'>{random_emojis[1]}</span>
</h3>
{info_section}
<div style='margin-top: 20px; display: flex; justify-content: space-between; align-items: center;'>
<div class="like-section" style="display: flex; align-items: center; gap: 10px;">
<button onclick="toggleLike('{card_id}')" class="like-button"
style="background: none; border: none; cursor: pointer; font-size: 1.5em; padding: 5px 10px;">
๐Ÿค
</button>
<span class="like-count" style="font-size: 1.2em; color: #666;">0</span>
</div>
<a href='{url}' target='_blank'
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
color: white;
padding: 10px 20px;
border-radius: 15px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0,132,255,0.3);'
onmouseover='this.style.transform="scale(1.05)"; this.style.boxShadow="0 4px 12px rgba(0,132,255,0.4)"'
onmouseout='this.style.transform="scale(1)"; this.style.boxShadow="0 2px 8px rgba(0,132,255,0.3)"'>
<span>View Deployment</span> ๐Ÿš€ {random_emojis[0]}
</a>
</div>
</div>
"""
# Top Best URLs ์ •์˜
TOP_BEST_URLS = [
{
"url": "dekvxz.vercel.app",
"name": "[๊ฒŒ์ž„] ๋‹ค์ด์–ดํŠธ ํ—Œํ„ฐ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "jtufui.vercel.app",
"name": "[๊ฒŒ์ž„] ํ…Œ๋Ÿฌ๋ฆฌ์ŠคํŠธ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ggumim",
"name": "[MOUSE-II] ์ด๋ฏธ์ง€์— ํ•œ๊ธ€ ์ถœ๋ ฅ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "xabtnc.vercel.app",
"name": "[ChatGPT] ๋‚˜๋งŒ์˜ LLM",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ifbhdc",
"name": "[๊ฒŒ์ž„] ๋ณด์„ ํŒกํŒก",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "nxhquk.vercel.app",
"name": "[๊ฒŒ์ž„] ํ…ŒํŠธ๋ฆฌ์Šค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "bydcnd.vercel.app",
"name": "[๋ชจ๋ธ] 3D ๋ถ„์ž ๋ชจํ˜•",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "ijhama.vercel.app",
"name": "ํˆฌ์ž ํฌํŠธํด๋ฆฌ์˜ค ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "oschnl.vercel.app",
"name": "๋กœ๋˜ ๋ฒˆํ˜ธ ๋ถ„์„/์ถ”์ฒœ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "rzwzrq.vercel.app",
"name": "์—‘์…€/CSV ๋ฐ์ดํ„ฐ ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "twkqre.vercel.app",
"name": "[์šด์„ธ] ํƒ€๋กœ์นด๋“œ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "htwymz.vercel.app",
"name": "[๊ฒŒ์ž„] ์†Œ๋ฐฉํ—ฌ๊ธฐ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "mktmbn.vercel.app",
"name": "[๊ฒŒ์ž„] ์šฐ์ฃผ์ „์Ÿ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "euguwt.vercel.app",
"name": "[๊ฒŒ์ž„] ํฌ์„ธ์ด๋ˆ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "qmdzoh.vercel.app",
"name": "[๊ฒŒ์ž„] ํ•˜๋Š˜์„ ์ง€์ผœ๋ผ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "kofaqo.vercel.app",
"name": "[๊ฒŒ์ž„] ์šด์„ ์ถฉ๋Œ!",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "qoqqkq.vercel.app",
"name": "[๊ฒŒ์ž„] ๋‘๋”์ฅ ์žก๊ธฐ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "nmznel.vercel.app",
"name": "[๊ฒŒ์ž„] ๊ณ ์–‘์ด ์ „์šฉ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "psrrtp.vercel.app",
"name": "[๋Œ€์‹œ๋ณด๋“œ] ์„ธ๊ณ„ ์ธ๊ตฌ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "xxloav.vercel.app",
"name": "[๊ฒŒ์ž„] ๋ฒฝ๋Œ ๊นจ๊ธฐ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/edpaje",
"name": "[๊ฒŒ์ž„] ๊ธฐ์–ต๋ ฅ ์นด๋“œ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ixtidb",
"name": "AI ์š”๋ฆฌ์‚ฌ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "cnlzji.vercel.app",
"name": "๊ตญ๊ฐ€ ์ •๋ณด ๋น„๊ต",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "fazely.vercel.app",
"name": "Wikipedia ์ง€์‹ ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "pkzhbo.vercel.app",
"name": "์„ธ๊ณ„ ๊ตญ๊ฐ€๋ณ„ ์‹œ๊ฐ„๋Œ€",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "pammgl.vercel.app",
"name": "๋ณด๋„์ž๋ฃŒ ๋ฐฐํฌ ์„œ๋น„์Šค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://ktduhm.vercel.app/",
"name": "์ˆ˜ํ•™์„ ๊ทธ๋ž˜ํ”„๋กœ ์ดํ•ด",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "vjmfoy.vercel.app",
"name": "[๊ฒŒ์ž„] 3D ๋ฒฝ๋Œ์Œ“๊ธฐ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "aodakf.vercel.app",
"name": "[๋ฒ„์ถ”์–ผ] 3D ๊ฐ€์ƒํ˜„์‹ค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "mxoeue.vercel.app",
"name": "์Œ์„ฑ ์ƒ์„ฑ(TTS),์กฐ์ •",
"created": "2024-11-18 00:00",
"state": "READY"
}
]
def get_user_spaces():
# ๊ธฐ์กด Hugging Face ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
url = f"https://huggingface.co/api/spaces?author={USERNAME}&limit=500"
headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try:
# Hugging Face ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
response = requests.get(url, headers=headers)
spaces_data = response.json() if response.status_code == 200 else []
# ์ œ์™ธํ•  ์ŠคํŽ˜์ด์Šค ํ•„ํ„ฐ๋ง
user_spaces = [
space for space in spaces_data
if not should_exclude_space(space.get('id', '').split('/')[-1])
]
# TOP_BEST_URLS ํ•ญ๋ชฉ ์ˆ˜
top_best_count = len(TOP_BEST_URLS)
# Vercel API๋ฅผ ํ†ตํ•œ ์‹ค์ œ ๋ฐฐํฌ ์ˆ˜
vercel_deployments = get_vercel_deployments()
actual_vercel_count = len(vercel_deployments) if vercel_deployments else 0
html_content = f"""
<div style='padding: 20px; background-color: #f5f5f5;'>
<div style='margin-bottom: 20px;'>
<h2 style='color: #333; margin: 0 0 5px 0;'>๊ณต๊ฐœ ๊ฐค๋Ÿฌ๋ฆฌ(์ƒ์„ฑ Web/App) by MOUSE</h2>
<p style='color: #666; margin: 0 0 15px 0; font-size: 0.9em;'>
ํ”„๋กฌํ”„ํŠธ๋งŒ์œผ๋กœ ๋‚˜๋งŒ์˜ ์›น์„œ๋น„์Šค๋ฅผ ์ฆ‰์‹œ ์ƒ์„ฑํ•˜๋Š” MOUSE
<a href='https://openfree-mouse.hf.space' target='_blank'
style='color: #0084ff; text-decoration: none;'>
https://openfree-mouse.hf.space
</a>
</p>
<p style='color: #666; margin: 0;'>
Found {actual_vercel_count} Vercel deployments and {len(user_spaces)} Hugging Face spaces<br>
(Plus {top_best_count} featured items in Top Best section)
</p>
</div>
<!-- Top Best -->
<h3 style='color: #333; margin: 20px 0;'>๐Ÿ† Top Best</h3>
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
{"".join(get_vercel_card(
{"url": url["url"], "created": url["created"], "name": url["name"], "state": url["state"]},
idx,
is_top_best=True
) for idx, url in enumerate(TOP_BEST_URLS))}
</div>
<!-- Vercel Deployments -->
{f'''
<h3 style='color: #333; margin: 20px 0;'>โšก Vercel Deployments</h3>
<div id="vercel-container" style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
{"".join(get_vercel_card(dep, idx) for idx, dep in enumerate(vercel_deployments))}
</div>
''' if vercel_deployments else ''}
<!-- Hugging Face Spaces -->
<h3 style='color: #333; margin: 20px 0;'>๐Ÿค— Hugging Face Spaces</h3>
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
{"".join(get_space_card(space, idx) for idx, space in enumerate(user_spaces))}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {{
// ์ข‹์•„์š” ์ƒํƒœ ๋กœ๋“œ
function loadLikes() {{
const cards = document.querySelectorAll('.vercel-card');
cards.forEach(card => {{
const cardId = card.id;
const likes = localStorage.getItem(cardId) || 0;
card.querySelector('.like-count').textContent = likes;
card.dataset.likes = likes;
updateLikeButton(card, likes > 0);
}});
sortCards();
}}
// ์ข‹์•„์š” ๋ฒ„ํŠผ ํ† ๊ธ€
window.toggleLike = function(cardId) {{
const card = document.getElementById(cardId);
const likeCount = parseInt(localStorage.getItem(cardId) || 0);
const newCount = likeCount > 0 ? 0 : 1;
localStorage.setItem(cardId, newCount);
card.querySelector('.like-count').textContent = newCount;
card.dataset.likes = newCount;
updateLikeButton(card, newCount > 0);
sortCards();
}}
// ์ข‹์•„์š” ๋ฒ„ํŠผ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
function updateLikeButton(card, isLiked) {{
const button = card.querySelector('.like-button');
button.textContent = isLiked ? 'โค๏ธ' : '๐Ÿค';
}}
// ์นด๋“œ ์ •๋ ฌ
function sortCards() {{
const container = document.getElementById('vercel-container');
const cards = Array.from(container.children);
cards.sort((a, b) => {{
return parseInt(b.dataset.likes) - parseInt(a.dataset.likes);
}});
cards.forEach(card => container.appendChild(card));
}}
// ์ดˆ๊ธฐ ๋กœ๋“œ
loadLikes();
}});
</script>
"""
return html_content
except Exception as e:
print(f"Error: {str(e)}")
return f"""
<div style='padding: 20px; text-align: center; color: #666;'>
<h2>Error occurred while fetching spaces</h2>
<p>Error details: {str(e)}</p>
<p>Please try again later.</p>
</div>
"""
# Creating the Gradio interface
demo = gr.Blocks()
with demo:
html_output = gr.HTML(value=get_user_spaces())
if __name__ == "__main__":
demo.launch()