openfree's picture
Update app.py
3a0fa58 verified
raw
history blame
28.4 kB
import requests
import gradio as gr
from datetime import datetime
import random
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
import base64
import time
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)
# 명시적 대기: body 요소가 로드될 때까지 대기 (최대 10초)
try:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
except TimeoutException:
print(f"페이지 로딩 타임아웃: {url}")
# 추가 대기 시간 (1초)
time.sleep(1)
# 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")
return base64.b64encode(buffered.getvalue()).decode()
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()
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', '이름 없는 프로젝트')
# 카드 ID 생성
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
# Top Best 항목일 경우의 스크린샷 처리
screenshot_html = ""
if is_top_best:
try:
print(f"스크린샷 캡처 시도: {url}") # 디버깅용 로그
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>
"""
else:
print(f"스크린샷 캡처 실패: {url}") # 디버깅용 로그
except Exception as e:
print(f"스크린샷 처리 오류: {str(e)} for URL: {url}") # 디버깅용 로그
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> ✅ {deployment.get('state', 'N/A')}
</p>
<p style='margin: 8px 0;'>
<strong>Created:</strong> 📅 {format_timestamp(deployment.get('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()