from flask import Flask, render_template, request, jsonify import requests import os import time import random from collections import Counter app = Flask(__name__) # Generate dummy spaces in case of error def generate_dummy_spaces(count): """ API 호출 실패 시 예시용 더미 스페이스 생성 """ spaces = [] for i in range(count): spaces.append({ 'id': f'dummy/space-{i}', 'owner': 'dummy', 'title': f'Example Space {i+1}', 'description': 'Dummy space for fallback', 'likes': 100 - i, 'createdAt': '2023-01-01T00:00:00.000Z', 'hardware': 'cpu', 'user': { 'avatar_url': 'https://huggingface.co/front/thumbnails/huggingface/default-avatar.svg', 'name': 'dummyUser' } }) return spaces # Function to fetch Zero-GPU (CPU-based) Spaces from Huggingface with pagination def fetch_trending_spaces(offset=0, limit=72): """ 원본 코드에서 수정: hardware=cpu 파라미터를 추가하여 GPU가 없는(=CPU 전용) 스페이스만 필터링 """ try: url = "https://huggingface.co/api/spaces" params = { "limit": 10000, # 더 많이 가져오기 "hardware": "cpu" # <-- Zero GPU(=CPU) 필터 적용 # "sort": "trending", # 필요시 추가 (HF API 지원 여부에 따라) } response = requests.get(url, params=params, timeout=30) if response.status_code == 200: spaces = response.json() # owner나 id가 'None'인 경우 제외 filtered_spaces = [ space for space in spaces if space.get('owner') != 'None' and space.get('id', '').split('/', 1)[0] != 'None' ] # Slice according to requested offset and limit start = min(offset, len(filtered_spaces)) end = min(offset + limit, len(filtered_spaces)) print(f"[fetch_trending_spaces] CPU기반 스페이스 총 {len(filtered_spaces)}개, " f"요청 구간 {start}~{end-1} 반환") return { 'spaces': filtered_spaces[start:end], 'total': len(filtered_spaces), 'offset': offset, 'limit': limit, 'all_spaces': filtered_spaces # 통계 산출용 } else: print(f"Error fetching spaces: {response.status_code}") # 실패 시 더미 데이터 생성 return { 'spaces': generate_dummy_spaces(limit), 'total': 200, 'offset': offset, 'limit': limit, 'all_spaces': generate_dummy_spaces(500) } except Exception as e: print(f"Exception when fetching spaces: {e}") # 실패 시 더미 데이터 생성 return { 'spaces': generate_dummy_spaces(limit), 'total': 200, 'offset': offset, 'limit': limit, 'all_spaces': generate_dummy_spaces(500) } # Transform Huggingface URL to direct space URL def transform_url(owner, name): """ Hugging Face Space -> 서브도메인 접근 URL 예) huggingface.co/spaces/owner/spaceName -> owner-spacename.hf.space """ # 1. Replace '.' with '-' name = name.replace('.', '-') # 2. Replace '_' with '-' name = name.replace('_', '-') # 3. Convert to lowercase owner = owner.lower() name = name.lower() return f"https://{owner}-{name}.hf.space" # Get space details def get_space_details(space_data, index, offset): """ 원본 코드에서 수정: - description, avatar_url, author_name 등 추가 필드를 추출 """ try: if '/' in space_data.get('id', ''): owner, name = space_data.get('id', '').split('/', 1) else: owner = space_data.get('owner', '') name = space_data.get('id', '') # Ignore if contains None if owner == 'None' or name == 'None': return None # Construct URLs original_url = f"https://huggingface.co/spaces/{owner}/{name}" embed_url = transform_url(owner, name) # Likes count likes_count = space_data.get('likes', 0) # Title title = space_data.get('title') or name # Description short_desc = space_data.get('description', '') # User info (avatar, name) user_info = space_data.get('user', {}) avatar_url = user_info.get('avatar_url', '') author_name = user_info.get('name') or owner return { 'url': original_url, 'embedUrl': embed_url, 'title': title, 'owner': owner, 'name': name, 'likes_count': likes_count, 'description': short_desc, 'avatar_url': avatar_url, 'author_name': author_name, 'rank': offset + index + 1 } except Exception as e: print(f"Error processing space data: {e}") # Return basic object even if error occurs return { 'url': 'https://huggingface.co/spaces', 'embedUrl': 'https://huggingface.co/spaces', 'title': 'Error Loading Space', 'owner': 'huggingface', 'name': 'error', 'likes_count': 0, 'description': '', 'avatar_url': '', 'author_name': 'huggingface', 'rank': offset + index + 1 } # Get owner statistics from all spaces def get_owner_stats(all_spaces): """ 모든 스페이스에서 owner 등장 빈도 상위 30명 추출 """ owners = [] for space in all_spaces: if '/' in space.get('id', ''): owner, _ = space.get('id', '').split('/', 1) else: owner = space.get('owner', '') if owner != 'None': owners.append(owner) # Count occurrences of each owner owner_counts = Counter(owners) # Get top 30 owners by count top_owners = owner_counts.most_common(30) return top_owners # Homepage route @app.route('/') def home(): """ index.html 템플릿 렌더링 (메인 페이지) """ return render_template('index.html') # Zero-GPU spaces API (원본: 'trending-spaces' -> 이제 CPU only) @app.route('/api/trending-spaces', methods=['GET']) def trending_spaces(): """ hardware=cpu 스페이스 목록을 불러와 검색, 페이징, 통계 등을 적용 """ search_query = request.args.get('search', '').lower() offset = int(request.args.get('offset', 0)) limit = int(request.args.get('limit', 72)) # Default 72 # Fetch zero-gpu (cpu) spaces spaces_data = fetch_trending_spaces(offset, limit) # Process and filter spaces results = [] for index, space_data in enumerate(spaces_data['spaces']): space_info = get_space_details(space_data, index, offset) if not space_info: continue # Apply search filter if needed if search_query: if (search_query not in space_info['title'].lower() and search_query not in space_info['owner'].lower() and search_query not in space_info['url'].lower() and search_query not in space_info['description'].lower()): continue results.append(space_info) # Get owner statistics for all spaces top_owners = get_owner_stats(spaces_data.get('all_spaces', [])) return jsonify({ 'spaces': results, 'total': spaces_data['total'], 'offset': offset, 'limit': limit, 'top_owners': top_owners }) if __name__ == '__main__': """ 서버 구동 시, templates/index.html 파일을 생성 후 Flask 실행 """ # Create templates folder if not exists os.makedirs('templates', exist_ok=True) # index.html 전체를 새로 작성 with open('templates/index.html', 'w', encoding='utf-8') as f: f.write(''' Huggingface Zero-GPU Spaces
Huggingface Explorer

Zero GPU Spaces

Discover 'TOP 1,000' Zero GPU(Shared A100) spaces from Hugging Face

Creator Statistics
Top 30 Creators by Count Number of TOP 1000 RANK
Loading Zero-GPU spaces...
If this takes too long, try refreshing the page.
''') # Flask 앱 실행 (포트 7860) app.run(host='0.0.0.0', port=7860)