Spaces:
Paused
Paused
| import requests | |
| import gradio as gr | |
| from datetime import datetime | |
| import random | |
| 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" | |
| url = "https://api.vercel.com/v6/deployments" | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json" | |
| } | |
| try: | |
| response = requests.get(url, headers=headers) | |
| if response.status_code != 200: | |
| print(f"Vercel API Error: {response.text}") | |
| return [] | |
| deployments = response.json().get('deployments', []) | |
| # 상태가 'READY'이고 'url'이 있는 배포만 필터링하고 'javis1' 제외 | |
| active_deployments = [ | |
| dep for dep in deployments | |
| if dep.get('state') == 'READY' and | |
| dep.get('url') and | |
| 'javis1' not in dep.get('name', '').lower() # javis1 제외 | |
| ] | |
| return active_deployments | |
| except Exception as e: | |
| print(f"Error fetching Vercel deployments: {str(e)}") | |
| return [] | |
| def get_vercel_card(deployment, index): | |
| """Generate HTML card for a Vercel deployment with like button""" | |
| raw_url = deployment.get('url', '') | |
| project_name = raw_url[:6] if len(raw_url) >= 6 else raw_url | |
| url = f"{project_name}.vercel.app" | |
| created = format_timestamp(deployment.get('created')) | |
| name = deployment.get('name', 'Unnamed Project') | |
| state = deployment.get('state', 'N/A') | |
| # 고유 ID 생성 (카드 식별용) | |
| card_id = f"vercel-card-{project_name}" | |
| bg_color = get_pastel_color(index + 20) | |
| tech_emojis = ['⚡', '🚀', '🌟', '✨', '💫', '🔥', '🌈', '🎯', '🎨', '🔮'] | |
| random_emojis = random.sample(tech_emojis, 3) | |
| 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)"'> | |
| <div style='position: absolute; top: -15px; right: -15px; font-size: 100px; opacity: 0.1;'> | |
| {random_emojis[0]} | |
| </div> | |
| <div style='position: absolute; top: 10px; right: 10px; font-size: 20px;'> | |
| {random_emojis[1]} | |
| </div> | |
| <div style='position: absolute; bottom: 10px; left: 10px; font-size: 20px;'> | |
| {random_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://{url}' target='_blank' | |
| style='text-decoration: none; color: #2d2d2d;'> | |
| {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>Status:</strong> ✅ {state} | |
| </p> | |
| <p style='margin: 8px 0;'> | |
| <strong>Created:</strong> 📅 {created} | |
| </p> | |
| <p style='margin: 8px 0;'> | |
| <strong>URL:</strong> 🔗 https://{url} | |
| </p> | |
| </div> | |
| <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='https://{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> | |
| """ | |
| # get_user_spaces 함수 수정 | |
| 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]) | |
| ] | |
| # Vercel 배포 가져오기 | |
| vercel_deployments = get_vercel_deployments() | |
| 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 {len(vercel_deployments)} Vercel deployments and {len(user_spaces)} Hugging Face spaces | |
| </p> | |
| </div> | |
| <!-- Vercel Deployments --> | |
| <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> | |
| <!-- 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() |