seawolf2357's picture
Update app.py
ada9833 verified
raw
history blame
14.4 kB
from flask import Flask, render_template, request, jsonify
import requests
import os
import json
import time
app = Flask(__name__)
# Function to fetch trending spaces from Huggingface
def fetch_trending_spaces(limit=100):
try:
# ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ (์›๋ž˜์˜ API ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ)
url = "https://huggingface.co/api/spaces"
params = {
"limit": limit,
"sort": "trending" # ๋‹จ์ˆœํžˆ trending ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒ ์‚ฌ์šฉ
}
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
# None ๊ฐ’์ด ์žˆ๋Š” ํ•ญ๋ชฉ ํ•„ํ„ฐ๋ง
spaces = response.json()
filtered_spaces = [space for space in spaces if space.get('owner') != 'None' and space.get('id', '').split('/', 1)[0] != 'None']
return filtered_spaces
else:
print(f"Error fetching trending spaces: {response.status_code}")
return []
except Exception as e:
print(f"Exception when fetching trending spaces: {e}")
return []
# Transform Huggingface URL to direct space URL
def transform_url(owner, name):
return f"https://{owner}-{name}.hf.space"
# Get space details
def get_space_details(space_data):
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', '')
# None์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ ๋ฌด์‹œ
if owner == 'None' or name == 'None':
return None
# URL ๊ตฌ์„ฑ
original_url = f"https://huggingface.co/spaces/{owner}/{name}"
embed_url = transform_url(owner, name)
# ์ข‹์•„์š” ์ˆ˜
likes_count = space_data.get('likes', 0)
# ์ œ๋ชฉ ์ถ”์ถœ
title = space_data.get('title', name)
# ํƒœ๊ทธ
tags = space_data.get('tags', [])
return {
'url': original_url,
'embedUrl': embed_url,
'title': title,
'owner': owner,
'likes_count': likes_count,
'tags': tags
}
except Exception as e:
print(f"Error processing space data: {e}")
return None
# Homepage route
@app.route('/')
def home():
return render_template('index.html')
# Trending spaces API
@app.route('/api/trending-spaces', methods=['GET'])
def trending_spaces():
search_query = request.args.get('search', '').lower()
limit = int(request.args.get('limit', 100))
# Fetch trending spaces
spaces_data = fetch_trending_spaces(limit)
# Process and filter spaces
results = []
for index, space_data in enumerate(spaces_data):
space_info = get_space_details(space_data)
if not space_info:
continue
# Apply search filter if needed
if search_query:
title = space_info['title'].lower()
owner = space_info['owner'].lower()
url = space_info['url'].lower()
tags = ' '.join([str(tag) for tag in space_info.get('tags', [])]).lower()
if (search_query not in title and
search_query not in owner and
search_query not in url and
search_query not in tags):
continue
results.append(space_info)
return jsonify(results)
if __name__ == '__main__':
# Create templates folder
os.makedirs('templates', exist_ok=True)
# Create index.html file
with open('templates/index.html', 'w', encoding='utf-8') as f:
f.write('''
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>์ธ๊ธฐ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
color: #333;
background-color: #f5f8fa;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 1rem;
}
.header {
background-color: #ffffff;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
text-align: center;
}
.header h1 {
margin: 0;
color: #2c3e50;
font-size: 1.8rem;
}
.filter-controls {
background-color: #ffffff;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
input[type="text"] {
padding: 0.7rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
width: 300px;
}
button.refresh-btn {
padding: 0.7rem 1.2rem;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 5px;
}
button.refresh-btn:hover {
background-color: #45a049;
}
.refresh-icon {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid white;
border-top-color: transparent;
border-radius: 50%;
animation: none;
}
.refreshing .refresh-icon {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
gap: 1.5rem;
}
.grid-item {
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
height: 600px;
position: relative;
}
.grid-header {
padding: 0.8rem;
border-bottom: 1px solid #eee;
background-color: #f9f9f9;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 2;
}
.grid-header-left {
display: flex;
flex-direction: column;
max-width: 70%;
}
.grid-header h3 {
margin: 0;
padding: 0;
font-size: 1.1rem;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.owner-info {
font-size: 0.85rem;
color: #666;
margin-top: 3px;
}
.grid-actions {
display: flex;
align-items: center;
}
.open-link {
color: #4CAF50;
text-decoration: none;
font-size: 0.9rem;
margin-left: 10px;
}
.likes-counter {
display: flex;
align-items: center;
font-size: 0.9rem;
color: #e91e63;
}
.likes-counter span {
margin-left: 4px;
}
.grid-content {
flex: 1;
position: relative;
overflow: hidden;
}
.grid-content iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
font-size: 1.5rem;
}
.loading-spinner {
border: 5px solid #f3f3f3;
border-top: 5px solid #4CAF50;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@media (max-width: 768px) {
.filter-controls {
flex-direction: column;
align-items: flex-start;
}
.filter-controls > * {
margin-bottom: 0.5rem;
width: 100%;
}
.grid-container {
grid-template-columns: 1fr;
}
input[type="text"] {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>์ธ๊ธฐ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค Top 100</h1>
</div>
<div class="filter-controls">
<input type="text" id="searchInput" placeholder="์ด๋ฆ„, ํƒœ๊ทธ ๋“ฑ์œผ๋กœ ๊ฒ€์ƒ‰" />
<button id="refreshButton" class="refresh-btn">
<span class="refresh-icon"></span>
์ƒˆ๋กœ๊ณ ์นจ
</button>
</div>
<div id="gridContainer" class="grid-container"></div>
</div>
<div id="loadingIndicator" class="loading">
<div class="loading-spinner"></div>
</div>
<script>
// DOM element references
const elements = {
gridContainer: document.getElementById('gridContainer'),
loadingIndicator: document.getElementById('loadingIndicator'),
searchInput: document.getElementById('searchInput'),
refreshButton: document.getElementById('refreshButton')
};
// Application state
const state = {
isLoading: false,
spaces: []
};
// Display loading indicator
function setLoading(isLoading) {
state.isLoading = isLoading;
elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
if (isLoading) {
elements.refreshButton.classList.add('refreshing');
} else {
elements.refreshButton.classList.remove('refreshing');
}
}
// API error handling
async function handleApiResponse(response) {
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API ์˜ค๋ฅ˜ (${response.status}): ${errorText}`);
}
return response.json();
}
// Load spaces
async function loadSpaces() {
setLoading(true);
try {
const searchText = elements.searchInput.value;
const response = await fetch(`/api/trending-spaces?search=${encodeURIComponent(searchText)}`);
const spaces = await handleApiResponse(response);
state.spaces = spaces;
renderGrid(spaces);
} catch (error) {
console.error('์ŠคํŽ˜์ด์Šค ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
alert(`์ŠคํŽ˜์ด์Šค ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`);
} finally {
setLoading(false);
}
}
// Render grid
function renderGrid(spaces) {
elements.gridContainer.innerHTML = '';
if (!spaces || spaces.length === 0) {
const noResultsMsg = document.createElement('p');
noResultsMsg.textContent = 'ํ‘œ์‹œํ•  ์ŠคํŽ˜์ด์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.';
noResultsMsg.style.padding = '1rem';
noResultsMsg.style.fontStyle = 'italic';
elements.gridContainer.appendChild(noResultsMsg);
return;
}
spaces.forEach((item, index) => {
const { url, embedUrl, title, likes_count, owner } = item;
// Skip if owner is 'None'
if (owner === 'None') {
return;
}
// Create grid item
const gridItem = document.createElement('div');
gridItem.className = 'grid-item';
// Header with title and actions
const header = document.createElement('div');
header.className = 'grid-header';
// Left side of header (title and owner)
const headerLeft = document.createElement('div');
headerLeft.className = 'grid-header-left';
const titleEl = document.createElement('h3');
titleEl.textContent = title;
titleEl.title = title; // For tooltip on hover
headerLeft.appendChild(titleEl);
const ownerEl = document.createElement('div');
ownerEl.className = 'owner-info';
ownerEl.textContent = `by: ${owner}`;
headerLeft.appendChild(ownerEl);
header.appendChild(headerLeft);
// Actions container
const actionsDiv = document.createElement('div');
actionsDiv.className = 'grid-actions';
// Likes count
const likesCounter = document.createElement('div');
likesCounter.className = 'likes-counter';
likesCounter.innerHTML = 'โ™ฅ <span>' + likes_count + '</span>';
actionsDiv.appendChild(likesCounter);
// Open link
const linkEl = document.createElement('a');
linkEl.href = url;
linkEl.target = '_blank';
linkEl.className = 'open-link';
linkEl.textContent = '์ƒˆ ์ฐฝ์—์„œ ์—ด๊ธฐ';
actionsDiv.appendChild(linkEl);
header.appendChild(actionsDiv);
// Add header to grid item
gridItem.appendChild(header);
// Content with iframe
const content = document.createElement('div');
content.className = 'grid-content';
// Create iframe to display the content
const iframe = document.createElement('iframe');
iframe.src = embedUrl;
iframe.title = title;
iframe.allow = 'accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi';
iframe.setAttribute('allowfullscreen', '');
iframe.setAttribute('frameborder', '0');
iframe.loading = 'lazy'; // Lazy load iframes for better performance
content.appendChild(iframe);
// Add content to grid item
gridItem.appendChild(content);
// Add grid item to container
elements.gridContainer.appendChild(gridItem);
});
}
// Filter event listeners
elements.searchInput.addEventListener('input', () => {
// Debounce input to prevent API calls on every keystroke
clearTimeout(state.searchTimeout);
state.searchTimeout = setTimeout(loadSpaces, 300);
});
// Refresh button event listener
elements.refreshButton.addEventListener('click', loadSpaces);
// Initialize
loadSpaces();
</script>
</body>
</html>
''')
# Use port 7860 for Huggingface Spaces
app.run(host='0.0.0.0', port=7860)