Download dashboard.html from FincH12/Hugging8N: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/spaces/FincH12/Hugging8N/resolve/main/dashboard.html
- Command line
-
hf download hf://spaces/FincH12/Hugging8N/dashboard.html
-
curl -L -o dashboard.html https://huggingface.co/spaces/FincH12/Hugging8N/resolve/main/dashboard.html
13.9 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Threads Automation Dashboard</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-color: #0f1115; | |
| --glass-bg: rgba(255, 255, 255, 0.03); | |
| --glass-border: rgba(255, 255, 255, 0.08); | |
| --accent-green: #00ff88; | |
| --accent-purple: #9d4edd; | |
| --text-main: #ffffff; | |
| --text-muted: #8b949e; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-color); | |
| background-image: | |
| radial-gradient(circle at 15% 50%, rgba(157, 78, 221, 0.15), transparent 25%), | |
| radial-gradient(circle at 85% 30%, rgba(0, 255, 136, 0.1), transparent 25%); | |
| background-attachment: fixed; | |
| color: var(--text-main); | |
| min-height: 100vh; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 3rem; | |
| border-bottom: 1px solid var(--glass-border); | |
| padding-bottom: 1.5rem; | |
| } | |
| h1 { | |
| margin: 0; | |
| font-size: 1.8rem; | |
| font-weight: 700; | |
| letter-spacing: -0.5px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .status-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 16px; | |
| background: rgba(0, 255, 136, 0.1); | |
| border: 1px solid rgba(0, 255, 136, 0.2); | |
| border-radius: 20px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| color: var(--accent-green); | |
| box-shadow: 0 0 10px rgba(0, 255, 136, 0.2); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| background-color: var(--accent-green); | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px var(--accent-green); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(0.95); opacity: 1; } | |
| 50% { transform: scale(1.2); opacity: 0.7; } | |
| 100% { transform: scale(0.95); opacity: 1; } | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: 1fr 2fr; | |
| gap: 2rem; | |
| } | |
| @media (max-width: 900px) { | |
| .grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .glass-panel { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); | |
| transition: transform 0.3s ease, border-color 0.3s ease; | |
| } | |
| .glass-panel:hover { | |
| border-color: rgba(255, 255, 255, 0.15); | |
| transform: translateY(-2px); | |
| } | |
| .panel-title { | |
| margin-top: 0; | |
| margin-bottom: 1.5rem; | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .stat-card { | |
| background: rgba(0, 0, 0, 0.2); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| margin-bottom: 0.5rem; | |
| } | |
| .stat-label { | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .terminal { | |
| background: #0d1117; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| font-family: 'Fira Code', monospace, Consolas; | |
| font-size: 0.85rem; | |
| color: #c9d1d9; | |
| height: 500px; | |
| overflow-y: auto; | |
| border: 1px solid #30363d; | |
| box-shadow: inset 0 0 20px rgba(0,0,0,0.5); | |
| line-height: 1.5; | |
| } | |
| .terminal::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .terminal::-webkit-scrollbar-track { | |
| background: #0d1117; | |
| } | |
| .terminal::-webkit-scrollbar-thumb { | |
| background: #30363d; | |
| border-radius: 4px; | |
| } | |
| .top-posts-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .post-item { | |
| background: rgba(0, 0, 0, 0.2); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 8px; | |
| padding: 1rem; | |
| } | |
| .post-text { | |
| font-size: 0.9rem; | |
| color: var(--text-main); | |
| margin-bottom: 0.8rem; | |
| line-height: 1.4; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 3; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .post-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 0.8rem; | |
| } | |
| .meta-badge { | |
| background: rgba(157, 78, 221, 0.15); | |
| color: #d8b4fe; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| font-weight: 600; | |
| } | |
| .meta-likes { | |
| color: var(--text-muted); | |
| } | |
| /* Terminal Syntax Highlighting */ | |
| .log-error { color: #ff7b72; } | |
| .log-success { color: #3fb950; } | |
| .log-warning { color: #d2a8ff; } | |
| .log-info { color: #79c0ff; } | |
| .log-time { color: #8b949e; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1> | |
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/> | |
| </svg> | |
| Threads AI Automation | |
| </h1> | |
| <div class="status-badge"> | |
| <div class="status-dot"></div> | |
| System Active | |
| </div> | |
| </header> | |
| <div class="grid"> | |
| <div class="left-col"> | |
| <div class="glass-panel" style="margin-bottom: 2rem;"> | |
| <h2 class="panel-title"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="18" y1="20" x2="18" y2="10"></line> | |
| <line x1="12" y1="20" x2="12" y2="4"></line> | |
| <line x1="6" y1="20" x2="6" y2="14"></line> | |
| </svg> | |
| Analytics | |
| </h2> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="val-posts">0</div> | |
| <div class="stat-label">Total Posts</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="val-likes">0</div> | |
| <div class="stat-label">Total Likes</div> | |
| </div> | |
| <div class="stat-card" style="grid-column: span 2;"> | |
| <div class="stat-value" id="val-replies">0</div> | |
| <div class="stat-label">Total Comments</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass-panel"> | |
| <h2 class="panel-title"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon> | |
| </svg> | |
| Hall of Fame | |
| </h2> | |
| <ul class="top-posts-list" id="top-posts-container"> | |
| <li class="post-item" style="text-align: center; color: var(--text-muted); border: none;"> | |
| Waiting for data... | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="right-col"> | |
| <div class="glass-panel"> | |
| <h2 class="panel-title"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polyline points="4 17 10 11 4 5"></polyline> | |
| <line x1="12" y1="19" x2="20" y2="19"></line> | |
| </svg> | |
| Live System Logs | |
| </h2> | |
| <div class="terminal" id="terminal-window"> | |
| Initializing connection to bot server... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| function colorizeLog(line) { | |
| if (line.includes('Error') || line.includes('Exception') || line.includes('Traceback')) { | |
| return `<span class="log-error">${line}</span>`; | |
| } else if (line.includes('SUCCESS') || line.includes('успешно') || line.includes('одобрен')) { | |
| return `<span class="log-success">${line}</span>`; | |
| } else if (line.includes('Predictor')) { | |
| return `<span class="log-warning">${line}</span>`; | |
| } else if (line.includes('Scheduler') || line.includes('Keep-Alive')) { | |
| return `<span class="log-info">${line}</span>`; | |
| } | |
| return line; | |
| } | |
| async function fetchLogs() { | |
| try { | |
| const response = await fetch('/api/logs'); | |
| const data = await response.json(); | |
| const term = document.getElementById('terminal-window'); | |
| // Only update if logs changed to prevent scrolling issues if not needed | |
| const formattedLogs = data.logs.split('\n').map(l => colorizeLog(l)).join('<br>'); | |
| if (term.innerHTML !== formattedLogs) { | |
| const isScrolledToBottom = term.scrollHeight - term.clientHeight <= term.scrollTop + 1; | |
| term.innerHTML = formattedLogs; | |
| // Auto-scroll if user was already at the bottom | |
| if (isScrolledToBottom) { | |
| term.scrollTop = term.scrollHeight; | |
| } | |
| } | |
| } catch (err) { | |
| console.error("Failed to fetch logs:", err); | |
| } | |
| } | |
| async function fetchStats() { | |
| try { | |
| const response = await fetch('/api/stats'); | |
| const data = await response.json(); | |
| document.getElementById('val-posts').innerText = data.total_posts; | |
| document.getElementById('val-likes').innerText = data.total_likes; | |
| document.getElementById('val-replies').innerText = data.total_replies; | |
| const topPostsContainer = document.getElementById('top-posts-container'); | |
| if (data.top_posts && data.top_posts.length > 0) { | |
| topPostsContainer.innerHTML = ''; | |
| data.top_posts.forEach(post => { | |
| const li = document.createElement('li'); | |
| li.className = 'post-item'; | |
| // Extract title from text if possible, otherwise use start of text | |
| let displayText = post.text; | |
| if (displayText.length > 150) displayText = displayText.substring(0, 150) + '...'; | |
| li.innerHTML = ` | |
| <div class="post-text">${displayText}</div> | |
| <div class="post-meta"> | |
| <span class="meta-badge">Score: ${post.predicted_score}/10</span> | |
| <span class="meta-likes">❤️ ${post.likes} | 💬 ${post.replies}</span> | |
| </div> | |
| `; | |
| topPostsContainer.appendChild(li); | |
| }); | |
| } | |
| } catch (err) { | |
| console.error("Failed to fetch stats:", err); | |
| } | |
| } | |
| // Initial fetch | |
| fetchLogs(); | |
| fetchStats(); | |
| // Polling | |
| setInterval(fetchLogs, 3000); | |
| setInterval(fetchStats, 10000); // Stats change less frequently | |
| </script> | |
| </body> | |
| </html> | |