ZeroGPU-Leader / app.py
seawolf2357's picture
Create app.py
d927a6e verified
raw
history blame
23.1 kB
from flask import Flask, render_template, request, redirect, url_for, jsonify, session
import requests
import os
import json
from datetime import timedelta
app = Flask(__name__)
app.secret_key = os.urandom(24) # ์„ธ์…˜ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ๋น„๋ฐ€ ํ‚ค
app.permanent_session_lifetime = timedelta(days=7) # ์„ธ์…˜ ์œ ์ง€ ๊ธฐ๊ฐ„ ์„ค์ •
# ํ—ˆ๊น…ํŽ˜์ด์Šค URL ๋ชฉ๋ก
HUGGINGFACE_URLS = [
"https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
"https://huggingface.co/spaces/openfree/deepseek_r1_API",
"https://huggingface.co/spaces/ginipick/open_Deep-Research",
"https://huggingface.co/spaces/aiqmaster/open-deep-research",
"https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search",
"https://huggingface.co/spaces/ginigen/LLaDA",
"https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
"https://huggingface.co/spaces/ginigen/Ovis2-8B",
"https://huggingface.co/spaces/ginigen/Graph-Mind",
"https://huggingface.co/spaces/ginigen/Workflow-Canvas",
"https://huggingface.co/spaces/ginigen/Design",
"https://huggingface.co/spaces/ginigen/Diagram",
"https://huggingface.co/spaces/ginigen/Mockup",
"https://huggingface.co/spaces/ginigen/Infographic",
"https://huggingface.co/spaces/ginigen/Flowchart",
"https://huggingface.co/spaces/aiqcamp/FLUX-Vision",
"https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
"https://huggingface.co/spaces/openfree/Perceptron-Network",
"https://huggingface.co/spaces/openfree/Article-Generator",
]
# URL์—์„œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ์ •๋ณด ์ถ”์ถœ
def extract_model_info(url):
parts = url.split('/')
if len(parts) < 6:
return None
if parts[3] == 'spaces' or parts[3] == 'models':
return {
'type': parts[3],
'owner': parts[4],
'repo': parts[5],
'full_id': f"{parts[4]}/{parts[5]}"
}
elif len(parts) >= 5:
# ๋‹ค๋ฅธ ํ˜•์‹์˜ URL
return {
'type': 'models', # ๊ธฐ๋ณธ๊ฐ’
'owner': parts[3],
'repo': parts[4],
'full_id': f"{parts[3]}/{parts[4]}"
}
return None
# URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ
def extract_title(url):
parts = url.split("/")
title = parts[-1] if parts else ""
return title.replace("_", " ").replace("-", " ")
# ํ—ˆ๊น…ํŽ˜์ด์Šค ์ธ์ฆ ํ™•์ธ
def validate_token(token):
headers = {"Authorization": f"Bearer {token}"}
try:
response = requests.get("https://huggingface.co/api/whoami", headers=headers)
return response.ok, response.json() if response.ok else None
except Exception as e:
print(f"ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {e}")
return False, None
# ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
def get_liked_repos(token):
headers = {"Authorization": f"Bearer {token}"}
endpoints = [
"/api/me/likes",
"/api/me/liked-repos",
"/api/me/favorites"
]
liked_models = {}
for endpoint in endpoints:
try:
response = requests.get(f"https://huggingface.co{endpoint}", headers=headers)
if response.ok:
data = response.json()
if isinstance(data, list):
for model in data:
if isinstance(model, dict):
if 'owner' in model and 'name' in model:
liked_models[f"{model['owner']}/{model['name']}"] = True
elif 'id' in model:
liked_models[model['id']] = True
elif isinstance(data, dict):
for key in data:
liked_models[key] = True
print(f"์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์„ฑ๊ณต (์—”๋“œํฌ์ธํŠธ: {endpoint})")
return liked_models
except Exception as e:
print(f"์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜ (์—”๋“œํฌ์ธํŠธ: {endpoint}): {e}")
return liked_models
# ์ข‹์•„์š” ํ† ๊ธ€ API ํ˜ธ์ถœ
def toggle_like(token, type_name, owner, repo, is_liked):
headers = {"Authorization": f"Bearer {token}"}
normalized_type = "spaces" if type_name == "spaces" else "models"
method = "DELETE" if is_liked else "POST"
url = f"https://huggingface.co/api/{normalized_type}/{owner}/{repo}/like"
try:
if method == "DELETE":
response = requests.delete(url, headers=headers)
else:
response = requests.post(url, headers=headers)
return response.ok, response.status_code
except Exception as e:
print(f"์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜: {e}")
return False, 500
# ํ™ˆํŽ˜์ด์ง€ ๋ผ์šฐํŠธ
@app.route('/')
def home():
return render_template('index.html')
# ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ API
@app.route('/api/login', methods=['POST'])
def login():
token = request.form.get('token', '')
if not token:
return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
is_valid, user_info = validate_token(token)
if not is_valid or not user_info:
return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'})
# ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ฐพ๊ธฐ
username = None
if 'name' in user_info:
username = user_info['name']
elif 'user' in user_info and 'username' in user_info['user']:
username = user_info['user']['username']
elif 'username' in user_info:
username = user_info['username']
else:
username = '์ธ์ฆ๋œ ์‚ฌ์šฉ์ž'
# ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
liked_models = get_liked_repos(token)
# ์„ธ์…˜์— ์ €์žฅ
session['token'] = token
session['username'] = username
session['liked_models'] = liked_models
return jsonify({
'success': True,
'username': username,
'liked_models': liked_models
})
# ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ API
@app.route('/api/logout', methods=['POST'])
def logout():
session.pop('token', None)
session.pop('username', None)
session.pop('liked_models', None)
return jsonify({'success': True})
# URL ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ API
@app.route('/api/urls', methods=['GET'])
def get_urls():
search_query = request.args.get('search', '').lower()
show_only_liked = request.args.get('liked', 'false').lower() == 'true'
liked_models = session.get('liked_models', {})
results = []
for url in HUGGINGFACE_URLS:
title = extract_title(url)
model_info = extract_model_info(url)
if not model_info:
continue
is_liked = model_info['full_id'] in liked_models
# ํ•„ํ„ฐ๋ง ์ ์šฉ
if show_only_liked and not is_liked:
continue
if search_query and search_query not in url.lower() and search_query not in title.lower():
continue
results.append({
'url': url,
'title': title,
'model_info': model_info,
'is_liked': is_liked
})
return jsonify(results)
# ์ข‹์•„์š” ํ† ๊ธ€ API
@app.route('/api/toggle-like', methods=['POST'])
def api_toggle_like():
if 'token' not in session:
return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
data = request.json
url = data.get('url')
current_liked = data.get('currentLiked', False)
if not url:
return jsonify({'success': False, 'message': 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
model_info = extract_model_info(url)
if not model_info:
return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ URL์ž…๋‹ˆ๋‹ค.'})
success, status_code = toggle_like(
session['token'],
model_info['type'],
model_info['owner'],
model_info['repo'],
current_liked
)
liked_models = session.get('liked_models', {})
if success:
# ์„ธ์…˜์˜ ์ข‹์•„์š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
if current_liked:
if model_info['full_id'] in liked_models:
del liked_models[model_info['full_id']]
else:
liked_models[model_info['full_id']] = True
session['liked_models'] = liked_models
return jsonify({
'success': True,
'new_state': not current_liked,
'message': f"{'์ข‹์•„์š” ์ทจ์†Œ' if current_liked else '์ข‹์•„์š”'} ์„ฑ๊ณต"
})
else:
return jsonify({
'success': False,
'message': f"API ์˜ค๋ฅ˜ (์ƒํƒœ ์ฝ”๋“œ: {status_code})"
})
# ์„ธ์…˜ ์ƒํƒœ ํ™•์ธ API
@app.route('/api/session-status', methods=['GET'])
def session_status():
return jsonify({
'logged_in': 'token' in session,
'username': session.get('username'),
'liked_count': len(session.get('liked_models', {}))
})
if __name__ == '__main__':
# templates ํด๋” ์ƒ์„ฑ
os.makedirs('templates', exist_ok=True)
# index.html ํŒŒ์ผ ์ƒ์„ฑ
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>Hugging Face URL ์นด๋“œ ๋ฆฌ์ŠคํŠธ</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
.header {
background-color: #f0f0f0;
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
.user-controls {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.filter-controls {
background-color: #f8f9fa;
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
input[type="password"],
input[type="text"] {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 5px;
}
button {
padding: 0.5rem 1rem;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
button.logout {
background-color: #f44336;
}
button.logout:hover {
background-color: #d32f2f;
}
.token-help {
margin-top: 0.5rem;
font-size: 0.8rem;
color: #666;
}
.token-help a {
color: #4CAF50;
text-decoration: none;
}
.token-help a:hover {
text-decoration: underline;
}
.cards-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
position: relative;
background-color: #f9f9f9;
}
.card a {
text-decoration: none;
color: #333;
word-break: break-all;
}
.like-button {
position: absolute;
top: 1rem;
right: 1rem;
border: none;
background: transparent;
font-size: 1.5rem;
cursor: pointer;
transition: color 0.2s;
}
.like-button.liked {
color: red;
}
.like-button.not-liked {
color: white;
-webkit-text-stroke: 1px #333;
}
.status-message {
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
display: none;
}
.success {
background-color: #dff0d8;
color: #3c763d;
}
.error {
background-color: #f2dede;
color: #a94442;
}
.loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
font-size: 1.5rem;
}
.login-section {
margin-top: 1rem;
}
.logged-in-section {
display: none;
margin-top: 1rem;
}
@media (max-width: 768px) {
.user-controls {
flex-direction: column;
align-items: flex-start;
}
.user-controls > div {
margin-bottom: 1rem;
}
.card {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="user-controls">
<div>
<span>ํ—ˆ๊น…ํŽ˜์ด์Šค ๊ณ„์ •: </span>
<span id="currentUser">๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ</span>
</div>
<div id="loginSection" class="login-section">
<input type="password" id="tokenInput" placeholder="ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ ์ž…๋ ฅ" />
<button id="loginButton">์ธ์ฆํ•˜๊ธฐ</button>
<div class="token-help">
API ํ† ํฐ์€ <a href="https://huggingface.co/settings/tokens" target="_blank">ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ํŽ˜์ด์ง€</a>์—์„œ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</div>
</div>
<div id="loggedInSection" class="logged-in-section">
<button id="logoutButton" class="logout">๋กœ๊ทธ์•„์›ƒ</button>
</div>
</div>
</div>
<div class="filter-controls">
<label>
<input type="checkbox" id="showOnlyLiked" />
๋‚ด๊ฐ€ ์ข‹์•„์š”ํ•œ URL๋งŒ ๋ณด๊ธฐ
</label>
<label style="margin-left: 1rem;">
<input type="text" id="searchInput" placeholder="URL ๋˜๋Š” ์ œ๋ชฉ์œผ๋กœ ๊ฒ€์ƒ‰" style="width: 250px;" />
</label>
</div>
<div id="statusMessage" class="status-message"></div>
<div id="loadingIndicator" class="loading">์ฒ˜๋ฆฌ ์ค‘...</div>
<div id="cardsContainer" class="cards-container"></div>
</div>
<script>
// DOM ์š”์†Œ ์ฐธ์กฐ
const elements = {
tokenInput: document.getElementById('tokenInput'),
loginButton: document.getElementById('loginButton'),
logoutButton: document.getElementById('logoutButton'),
currentUser: document.getElementById('currentUser'),
cardsContainer: document.getElementById('cardsContainer'),
loadingIndicator: document.getElementById('loadingIndicator'),
statusMessage: document.getElementById('statusMessage'),
showOnlyLiked: document.getElementById('showOnlyLiked'),
searchInput: document.getElementById('searchInput'),
loginSection: document.getElementById('loginSection'),
loggedInSection: document.getElementById('loggedInSection')
};
// ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ
const state = {
username: null,
likedModels: {},
isLoading: false
};
// ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ ํ•จ์ˆ˜
function setLoading(isLoading) {
state.isLoading = isLoading;
elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
}
// ์ƒํƒœ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜
function showMessage(message, isError = false) {
elements.statusMessage.textContent = message;
elements.statusMessage.className = `status-message ${isError ? 'error' : 'success'}`;
elements.statusMessage.style.display = 'block';
// 3์ดˆ ํ›„ ๋ฉ”์‹œ์ง€ ์‚ฌ๋ผ์ง
setTimeout(() => {
elements.statusMessage.style.display = 'none';
}, 3000);
}
// API ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
async function handleApiResponse(response) {
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API ์˜ค๋ฅ˜ (${response.status}): ${errorText}`);
}
return response.json();
}
// ์„ธ์…˜ ์ƒํƒœ ํ™•์ธ
async function checkSessionStatus() {
try {
const response = await fetch('/api/session-status');
const data = await handleApiResponse(response);
if (data.logged_in) {
state.username = data.username;
elements.currentUser.textContent = data.username;
elements.loginSection.style.display = 'none';
elements.loggedInSection.style.display = 'block';
// URL ๋ชฉ๋ก ๋กœ๋“œ
loadUrls();
}
} catch (error) {
console.error('์„ธ์…˜ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
}
}
// ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
async function login(token) {
if (!token.trim()) {
showMessage('ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', true);
return;
}
setLoading(true);
try {
const formData = new FormData();
formData.append('token', token);
const response = await fetch('/api/login', {
method: 'POST',
body: formData
});
const data = await handleApiResponse(response);
if (data.success) {
state.username = data.username;
state.likedModels = data.liked_models || {};
elements.currentUser.textContent = state.username;
elements.loginSection.style.display = 'none';
elements.loggedInSection.style.display = 'block';
showMessage(`${state.username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`);
// URL ๋ชฉ๋ก ๋กœ๋“œ
loadUrls();
} else {
showMessage(data.message || '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
}
} catch (error) {
console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error);
showMessage(`๋กœ๊ทธ์ธ ์˜ค๋ฅ˜: ${error.message}`, true);
} finally {
setLoading(false);
}
}
// ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
async function logout() {
setLoading(true);
try {
const response = await fetch('/api/logout', {
method: 'POST'
});
const data = await handleApiResponse(response);
if (data.success) {
state.username = null;
state.likedModels = {};
elements.currentUser.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
elements.tokenInput.value = '';
elements.loginSection.style.display = 'block';
elements.loggedInSection.style.display = 'none';
showMessage('๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
// ์นด๋“œ ์ดˆ๊ธฐํ™”
elements.cardsContainer.innerHTML = '';
}
} catch (error) {
console.error('๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜:', error);
showMessage(`๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜: ${error.message}`, true);
} finally {
setLoading(false);
}
}
// URL ๋ชฉ๋ก ๋กœ๋“œ
async function loadUrls() {
setLoading(true);
try {
const showOnlyLiked = elements.showOnlyLiked.checked;
const searchText = elements.searchInput.value;
const response = await fetch(`/api/urls?liked=${showOnlyLiked}&search=${encodeURIComponent(searchText)}`);
const urls = await handleApiResponse(response);
renderCards(urls);
} catch (error) {
console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
showMessage(`URL ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, true);
} finally {
setLoading(false);
}
}
// ์ข‹์•„์š” ํ† ๊ธ€
async function toggleLike(url, button, currentLiked) {
if (!state.username) {
showMessage('์ข‹์•„์š”๋ฅผ ํ•˜๋ ค๋ฉด ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ์œผ๋กœ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', true);
return;
}
setLoading(true);
try {
const response = await fetch('/api/toggle-like', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: url,
currentLiked: currentLiked
})
});
const data = await handleApiResponse(response);
if (data.success) {
if (currentLiked) {
button.classList.remove('liked');
button.classList.add('not-liked');
} else {
button.classList.add('liked');
button.classList.remove('not-liked');
}
showMessage(data.message);
// ํ•„ํ„ฐ๋ง๋œ ๊ฒฝ์šฐ ๋ชฉ๋ก ๋‹ค์‹œ ๋กœ๋“œ
if (elements.showOnlyLiked.checked) {
loadUrls();
}
} else {
showMessage(data.message, true);
}
} catch (error) {
console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error);
showMessage(`์ข‹์•„์š” ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: ${error.message}`, true);
} finally {
setLoading(false);
}
}
// ์นด๋“œ ๋ Œ๋”๋ง
function renderCards(urls) {
elements.cardsContainer.innerHTML = '';
if (!urls || urls.length === 0) {
const noResultsMsg = document.createElement('p');
noResultsMsg.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
noResultsMsg.style.padding = '1rem';
noResultsMsg.style.fontStyle = 'italic';
elements.cardsContainer.appendChild(noResultsMsg);
return;
}
urls.forEach(item => {
const { url, title, is_liked } = item;
// ์นด๋“œ ์ƒ์„ฑ
const card = document.createElement('div');
card.className = 'card';
// ์ œ๋ชฉ
const titleEl = document.createElement('h3');
titleEl.textContent = title;
card.appendChild(titleEl);
// URL ๋งํฌ
const linkEl = document.createElement('a');
linkEl.href = url;
linkEl.textContent = url;
linkEl.target = '_blank';
card.appendChild(linkEl);
// ์ข‹์•„์š” ๋ฒ„ํŠผ (โ™ฅ ์•„์ด์ฝ˜)
const likeBtn = document.createElement('button');
likeBtn.className = `like-button ${is_liked ? 'liked' : 'not-liked'}`;
likeBtn.textContent = 'โ™ฅ';
likeBtn.addEventListener('click', function(e) {
e.preventDefault();
toggleLike(url, likeBtn, is_liked);
});
card.appendChild(likeBtn);
// ์นด๋“œ ์ถ”๊ฐ€
elements.cardsContainer.appendChild(card);
});
}
// ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
elements.loginButton.addEventListener('click', () => {
login(elements.tokenInput.value);
});
elements.logoutButton.addEventListener('click', logout);
// ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ
elements.tokenInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
login(elements.tokenInput.value);
}
});
// ํ•„ํ„ฐ๋ง ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
elements.showOnlyLiked.addEventListener('change', loadUrls);
elements.searchInput.addEventListener('input', () => {
// ์ž…๋ ฅ ์ง€์—ฐ ์ฒ˜๋ฆฌ (ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค API ํ˜ธ์ถœ ๋ฐฉ์ง€)
clearTimeout(state.searchTimeout);
state.searchTimeout = setTimeout(loadUrls, 300);
});
// ์ดˆ๊ธฐํ™”
checkSessionStatus();
</script>
</body>
</html>
''')
# ์„œ๋ฒ„ ์‹คํ–‰
app.run(debug=True)