Spaces:
Sleeping
Sleeping
| # app.py์ __main__ ๋ถ๋ถ ์์ (index.html ํ ํ๋ฆฟ ์์ฑ ๋ถ๋ถ) | |
| if __name__ == '__main__': | |
| os.makedirs('templates', exist_ok=True) | |
| with open('templates/index.html', 'w', encoding='utf-8') as f: | |
| f.write(''' | |
| <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; | |
| background-color: #f4f5f7; | |
| } | |
| .container { | |
| max-width: 1600px; | |
| margin: 0 auto; | |
| padding: 1rem; | |
| } | |
| .header { | |
| background-color: #fff; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-bottom: 1rem; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .user-controls { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .filter-controls { | |
| background-color: #fff; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-bottom: 1rem; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| input[type="password"], | |
| input[type="text"] { | |
| padding: 0.5rem; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| margin-right: 5px; | |
| } | |
| button { | |
| padding: 0.5rem 1rem; | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| button:hover { | |
| background-color: #45a049; | |
| } | |
| button.refresh { | |
| background-color: #2196F3; | |
| } | |
| button.refresh:hover { | |
| background-color: #0b7dda; | |
| } | |
| 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; | |
| } | |
| /* ๊ทธ๋ฆฌ๋ ๋ ์ด์์ ์คํ์ผ */ | |
| .grid-container { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 1rem; | |
| } | |
| .grid-item { | |
| border: 1px solid #ddd; | |
| border-radius: 8px; | |
| background-color: #fff; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .grid-item:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| } | |
| .grid-item.liked { | |
| border-color: #ff4757; | |
| background-color: #ffebee; | |
| } | |
| .grid-header { | |
| padding: 0.5rem 1rem; | |
| border-bottom: 1px solid #eee; | |
| position: relative; | |
| } | |
| .grid-title { | |
| font-size: 1rem; | |
| margin: 0; | |
| padding-right: 30px; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .grid-content { | |
| flex: 1; | |
| position: relative; | |
| height: 300px; | |
| } | |
| .iframe-container { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .iframe-container iframe { | |
| width: 100%; | |
| height: 100%; | |
| border: none; | |
| } | |
| .like-button { | |
| position: absolute; | |
| top: 0.5rem; | |
| right: 0.5rem; | |
| width: 24px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 50%; | |
| border: none; | |
| background: transparent; | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| color: #ddd; | |
| padding: 0; | |
| } | |
| .like-button:hover { | |
| transform: scale(1.2); | |
| } | |
| .like-button.liked { | |
| color: #ff4757; | |
| } | |
| .like-badge { | |
| position: absolute; | |
| top: -5px; | |
| left: -5px; | |
| background-color: #ff4757; | |
| color: white; | |
| padding: 0.2rem 0.5rem; | |
| border-radius: 4px; | |
| font-size: 0.7rem; | |
| font-weight: bold; | |
| z-index: 10; | |
| } | |
| .like-status { | |
| background-color: #fff; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| margin-bottom: 1rem; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| display: none; | |
| } | |
| .like-status strong { | |
| color: #ff4757; | |
| } | |
| .status-message { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| display: none; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
| z-index: 1000; | |
| max-width: 300px; | |
| } | |
| .success { | |
| background-color: #4CAF50; | |
| color: white; | |
| } | |
| .error { | |
| background-color: #f44336; | |
| color: white; | |
| } | |
| .loading { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| display: none; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #3498db; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .filter-toggle { | |
| display: flex; | |
| } | |
| .filter-toggle button { | |
| margin-right: 0.5rem; | |
| background-color: #f0f0f0; | |
| color: #333; | |
| } | |
| .filter-toggle button.active { | |
| background-color: #4CAF50; | |
| color: white; | |
| } | |
| .login-section { | |
| margin-top: 1rem; | |
| } | |
| .logged-in-section { | |
| display: none; | |
| margin-top: 1rem; | |
| } | |
| .note { | |
| padding: 0.5rem; | |
| background-color: #fffde7; | |
| border-left: 3px solid #ffd600; | |
| margin-bottom: 1rem; | |
| font-size: 0.9rem; | |
| } | |
| .view-toggle { | |
| margin-top: 1rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .view-toggle button { | |
| margin-left: 0.5rem; | |
| } | |
| /* ๋ฐ์ํ ์ค์ */ | |
| @media (max-width: 1400px) { | |
| .grid-container { | |
| grid-template-columns: repeat(3, 1fr); | |
| } | |
| } | |
| @media (max-width: 1024px) { | |
| .grid-container { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .user-controls { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .user-controls > div { | |
| margin-bottom: 1rem; | |
| } | |
| .filter-controls { | |
| flex-direction: column; | |
| } | |
| .filter-controls > div { | |
| margin-bottom: 0.5rem; | |
| } | |
| .grid-container { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </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="refreshButton" class="refresh">์๋ก๊ณ ์นจ</button> | |
| <button id="logoutButton" class="logout">๋ก๊ทธ์์</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="note"> | |
| <p><strong>์ฐธ๊ณ :</strong> ์ด ํ์ด์ง๋ ์น ์คํฌ๋ํ ๋ฐฉ์์ผ๋ก ์ข์์ ์ํ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ข์์ ์ํ๊ฐ ์ ํํ์ง ์๊ฑฐ๋ ์ง์ฐ๋ ์ ์์ต๋๋ค. '์๋ก๊ณ ์นจ' ๋ฒํผ์ ํด๋ฆญํ์ฌ ์ต์ ์ํ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.</p> | |
| </div> | |
| <div id="likeStatus" class="like-status"> | |
| <div id="likeStatsText">์ด <span id="totalUrlCount">0</span>๊ฐ ์ค <strong><span id="likedUrlCount">0</span>๊ฐ</strong>์ URL์ ์ข์์ ํ์ต๋๋ค.</div> | |
| </div> | |
| <div class="filter-controls"> | |
| <div> | |
| <input type="text" id="searchInput" placeholder="URL ๋๋ ์ ๋ชฉ์ผ๋ก ๊ฒ์" style="width: 300px;" /> | |
| </div> | |
| <div class="filter-toggle"> | |
| <button id="allUrlsBtn" class="active">์ ์ฒด ๋ณด๊ธฐ</button> | |
| <button id="likedUrlsBtn">์ข์์๋ง ๋ณด๊ธฐ</button> | |
| </div> | |
| </div> | |
| <div class="view-toggle"> | |
| <div> | |
| <input type="checkbox" id="embedToggle" checked /> | |
| <label for="embedToggle">URL ์๋ฒ ๋ฉ ๋ณด๊ธฐ</label> | |
| </div> | |
| </div> | |
| <div id="statusMessage" class="status-message"></div> | |
| <div id="loadingIndicator" class="loading"> | |
| <div class="spinner"></div> | |
| </div> | |
| <div id="gridContainer" class="grid-container"></div> | |
| </div> | |
| <script> | |
| // DOM ์์ ์ฐธ์กฐ | |
| const elements = { | |
| tokenInput: document.getElementById('tokenInput'), | |
| loginButton: document.getElementById('loginButton'), | |
| logoutButton: document.getElementById('logoutButton'), | |
| refreshButton: document.getElementById('refreshButton'), | |
| currentUser: document.getElementById('currentUser'), | |
| gridContainer: document.getElementById('gridContainer'), | |
| loadingIndicator: document.getElementById('loadingIndicator'), | |
| statusMessage: document.getElementById('statusMessage'), | |
| searchInput: document.getElementById('searchInput'), | |
| loginSection: document.getElementById('loginSection'), | |
| loggedInSection: document.getElementById('loggedInSection'), | |
| likeStatus: document.getElementById('likeStatus'), | |
| totalUrlCount: document.getElementById('totalUrlCount'), | |
| likedUrlCount: document.getElementById('likedUrlCount'), | |
| allUrlsBtn: document.getElementById('allUrlsBtn'), | |
| likedUrlsBtn: document.getElementById('likedUrlsBtn'), | |
| embedToggle: document.getElementById('embedToggle') | |
| }; | |
| // ์ ํ๋ฆฌ์ผ์ด์ ์ํ | |
| const state = { | |
| username: null, | |
| allURLs: [], | |
| isLoading: false, | |
| viewMode: 'all', // 'all' ๋๋ 'liked' | |
| embedView: true // iframe ์๋ฒ ๋ฉ ์ฌ๋ถ | |
| }; | |
| // ๋ก๋ฉ ์ํ ํ์ ํจ์ | |
| 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(); | |
| } | |
| // ์ข์์ ํต๊ณ ์ ๋ฐ์ดํธ | |
| function updateLikeStats() { | |
| const totalCount = state.allURLs.length; | |
| const likedCount = state.allURLs.filter(item => item.is_liked).length; | |
| elements.totalUrlCount.textContent = totalCount; | |
| elements.likedUrlCount.textContent = likedCount; | |
| } | |
| // ์ธ์ ์ํ ํ์ธ | |
| 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'; | |
| elements.likeStatus.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; | |
| elements.currentUser.textContent = state.username; | |
| elements.loginSection.style.display = 'none'; | |
| elements.loggedInSection.style.display = 'block'; | |
| elements.likeStatus.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.allURLs = []; | |
| elements.currentUser.textContent = '๋ก๊ทธ์ธ๋์ง ์์'; | |
| elements.tokenInput.value = ''; | |
| elements.loginSection.style.display = 'block'; | |
| elements.loggedInSection.style.display = 'none'; | |
| elements.likeStatus.style.display = 'none'; | |
| showMessage('๋ก๊ทธ์์๋์์ต๋๋ค.'); | |
| // ๊ทธ๋ฆฌ๋ ์ด๊ธฐํ | |
| elements.gridContainer.innerHTML = ''; | |
| } | |
| } catch (error) { | |
| console.error('๋ก๊ทธ์์ ์ค๋ฅ:', error); | |
| showMessage(`๋ก๊ทธ์์ ์ค๋ฅ: ${error.message}`, true); | |
| } finally { | |
| setLoading(false); | |
| } | |
| } | |
| // ์ข์์ ์ํ ์๋ก๊ณ ์นจ | |
| async function refreshLikes() { | |
| setLoading(true); | |
| try { | |
| const response = await fetch('/api/refresh-likes', { | |
| method: 'POST' | |
| }); | |
| const data = await handleApiResponse(response); | |
| if (data.success) { | |
| // URL ๋ชฉ๋ก ๋ค์ ๋ก๋ | |
| loadUrls(); | |
| showMessage('์ข์์ ์ํ๊ฐ ์๋ก๊ณ ์นจ๋์์ต๋๋ค.'); | |
| } else { | |
| showMessage(data.message || '์ข์์ ์ํ ์๋ก๊ณ ์นจ์ ์คํจํ์ต๋๋ค.', true); | |
| } | |
| } catch (error) { | |
| console.error('์ข์์ ์ํ ์๋ก๊ณ ์นจ ์ค๋ฅ:', error); | |
| showMessage(`์ข์์ ์ํ ์๋ก๊ณ ์นจ ์ค๋ฅ: ${error.message}`, true); | |
| } finally { | |
| setLoading(false); | |
| } | |
| } | |
| // URL ๋ชฉ๋ก ๋ก๋ | |
| async function loadUrls() { | |
| setLoading(true); | |
| try { | |
| const response = await fetch('/api/urls'); | |
| const data = await handleApiResponse(response); | |
| state.allURLs = data; | |
| updateLikeStats(); | |
| renderGrid(); | |
| } catch (error) { | |
| console.error('URL ๋ชฉ๋ก ๋ก๋ ์ค๋ฅ:', error); | |
| showMessage(`URL ๋ชฉ๋ก ๋ก๋ ์ค๋ฅ: ${error.message}`, true); | |
| } finally { | |
| setLoading(false); | |
| } | |
| } | |
| // ์ข์์ ํ ๊ธ | |
| async function toggleLike(url) { | |
| try { | |
| const response = await fetch('/api/toggle-like', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ url }) | |
| }); | |
| const data = await handleApiResponse(response); | |
| if (data.success) { | |
| // URL ๊ฐ์ฒด ์ฐพ๊ธฐ | |
| const urlObj = state.allURLs.find(item => item.url === url); | |
| if (urlObj) { | |
| urlObj.is_liked = data.is_liked; | |
| updateLikeStats(); | |
| renderGrid(); | |
| } | |
| showMessage(data.message); | |
| } else { | |
| showMessage(data.message || '์ข์์ ์ํ ๋ณ๊ฒฝ์ ์คํจํ์ต๋๋ค.', true); | |
| } | |
| } catch (error) { | |
| console.error('์ข์์ ํ ๊ธ ์ค๋ฅ:', error); | |
| showMessage(`์ข์์ ํ ๊ธ ์ค๋ฅ: ${error.message}`, true); | |
| } | |
| } | |
| // ๊ทธ๋ฆฌ๋ ๋ ๋๋ง | |
| function renderGrid() { | |
| elements.gridContainer.innerHTML = ''; | |
| let urlsToShow = state.allURLs; | |
| // ๊ฒ์์ด๋ก ํํฐ๋ง | |
| const searchTerm = elements.searchInput.value.trim().toLowerCase(); | |
| if (searchTerm) { | |
| urlsToShow = urlsToShow.filter(item => | |
| item.url.toLowerCase().includes(searchTerm) || | |
| item.title.toLowerCase().includes(searchTerm) | |
| ); | |
| } | |
| // ๋ณด๊ธฐ ๋ชจ๋๋ก ํํฐ๋ง (์ ์ฒด ๋๋ ์ข์์๋ง) | |
| if (state.viewMode === 'liked') { | |
| urlsToShow = urlsToShow.filter(item => item.is_liked); | |
| } | |
| if (urlsToShow.length === 0) { | |
| const emptyMessage = document.createElement('div'); | |
| emptyMessage.textContent = 'ํ์ํ URL์ด ์์ต๋๋ค.'; | |
| emptyMessage.style.padding = '1rem'; | |
| emptyMessage.style.width = '100%'; | |
| emptyMessage.style.textAlign = 'center'; | |
| elements.gridContainer.appendChild(emptyMessage); | |
| return; | |
| } | |
| // ๊ทธ๋ฆฌ๋ ์์ดํ ์์ฑ | |
| urlsToShow.forEach(item => { | |
| const gridItem = document.createElement('div'); | |
| gridItem.className = `grid-item ${item.is_liked ? 'liked' : ''}`; | |
| if (item.is_liked) { | |
| const badge = document.createElement('div'); | |
| badge.className = 'like-badge'; | |
| badge.textContent = '์ข์์'; | |
| gridItem.appendChild(badge); | |
| } | |
| // ํค๋ ๋ถ๋ถ | |
| const header = document.createElement('div'); | |
| header.className = 'grid-header'; | |
| const title = document.createElement('h3'); | |
| title.className = 'grid-title'; | |
| title.textContent = item.title; | |
| const likeButton = document.createElement('button'); | |
| likeButton.className = `like-button ${item.is_liked ? 'liked' : ''}`; | |
| likeButton.innerHTML = 'โค'; | |
| likeButton.dataset.url = item.url; | |
| likeButton.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| toggleLike(item.url); | |
| }); | |
| header.appendChild(title); | |
| header.appendChild(likeButton); | |
| // ์ปจํ ์ธ ๋ถ๋ถ (iframe ์๋ฒ ๋ฉ) | |
| const content = document.createElement('div'); | |
| content.className = 'grid-content'; | |
| if (state.embedView) { | |
| const iframeContainer = document.createElement('div'); | |
| iframeContainer.className = 'iframe-container'; | |
| const iframe = document.createElement('iframe'); | |
| iframe.src = item.url; | |
| iframe.title = item.title; | |
| iframe.sandbox = 'allow-scripts allow-same-origin allow-forms'; | |
| iframe.loading = 'lazy'; | |
| iframeContainer.appendChild(iframe); | |
| content.appendChild(iframeContainer); | |
| } else { | |
| // ์๋ฒ ๋ฉ ์๋ ๊ฒฝ์ฐ ๋งํฌ๋ง ํ์ | |
| const linkContainer = document.createElement('div'); | |
| linkContainer.style.padding = '1rem'; | |
| const link = document.createElement('a'); | |
| link.href = item.url; | |
| link.textContent = item.url; | |
| link.target = '_blank'; | |
| const owner = document.createElement('div'); | |
| owner.textContent = `์์ ์: ${item.model_info.owner}`; | |
| owner.style.marginTop = '0.5rem'; | |
| const repo = document.createElement('div'); | |
| repo.textContent = `์ ์ฅ์: ${item.model_info.repo}`; | |
| const type = document.createElement('div'); | |
| type.textContent = `์ ํ: ${item.model_info.type}`; | |
| linkContainer.appendChild(link); | |
| linkContainer.appendChild(owner); | |
| linkContainer.appendChild(repo); | |
| linkContainer.appendChild(type); | |
| content.appendChild(linkContainer); | |
| } | |
| // ๊ทธ๋ฆฌ๋ ์์ดํ ์ ์ถ๊ฐ | |
| gridItem.appendChild(header); | |
| gridItem.appendChild(content); | |
| elements.gridContainer.appendChild(gridItem); | |
| }); | |
| } | |
| // ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก | |
| function registerEventListeners() { | |
| // ๋ก๊ทธ์ธ ๋ฒํผ | |
| elements.loginButton.addEventListener('click', () => { | |
| login(elements.tokenInput.value); | |
| }); | |
| // ์ํฐ ํค๋ก ๋ก๊ทธ์ธ | |
| elements.tokenInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| login(elements.tokenInput.value); | |
| } | |
| }); | |
| // ๋ก๊ทธ์์ ๋ฒํผ | |
| elements.logoutButton.addEventListener('click', logout); | |
| // ์๋ก๊ณ ์นจ ๋ฒํผ | |
| elements.refreshButton.addEventListener('click', refreshLikes); | |
| // ๊ฒ์ ์ ๋ ฅ ํ๋ | |
| elements.searchInput.addEventListener('input', renderGrid); | |
| // ํํฐ ๋ฒํผ - ์ ์ฒด ๋ณด๊ธฐ | |
| elements.allUrlsBtn.addEventListener('click', () => { | |
| elements.allUrlsBtn.classList.add('active'); | |
| elements.likedUrlsBtn.classList.remove('active'); | |
| state.viewMode = 'all'; | |
| renderGrid(); | |
| }); | |
| // ํํฐ ๋ฒํผ - ์ข์์๋ง ๋ณด๊ธฐ | |
| elements.likedUrlsBtn.addEventListener('click', () => { | |
| elements.likedUrlsBtn.classList.add('active'); | |
| elements.allUrlsBtn.classList.remove('active'); | |
| state.viewMode = 'liked'; | |
| renderGrid(); | |
| }); | |
| // ์๋ฒ ๋ฉ ํ ๊ธ | |
| elements.embedToggle.addEventListener('change', () => { | |
| state.embedView = elements.embedToggle.checked; | |
| renderGrid(); | |
| }); | |
| } | |
| // ์ด๊ธฐํ ํจ์ | |
| function init() { | |
| registerEventListeners(); | |
| checkSessionStatus(); | |
| } | |
| // ์ ํ๋ฆฌ์ผ์ด์ ์ด๊ธฐํ | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |
| ''') | |
| app.run(debug=True, host='0.0.0.0', port=5000) |