Download dashboard.html from FincH12/Automation_Threads_EU: direct link, hf CLI and curl.
- Browser
- Download file 25 kB
-
https://huggingface.co/spaces/FincH12/Automation_Threads_EU/resolve/main/dashboard.html
- Command line
-
hf download hf://spaces/FincH12/Automation_Threads_EU/dashboard.html
-
curl -L -o dashboard.html https://huggingface.co/spaces/FincH12/Automation_Threads_EU/resolve/main/dashboard.html
25 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Threads Automation Agent</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| :root { | |
| --bg-base: #050505; | |
| --bg-surface: rgba(18, 18, 20, 0.6); | |
| --bg-surface-hover: rgba(28, 28, 30, 0.8); | |
| --glass-border: rgba(255, 255, 255, 0.08); | |
| --accent-primary: #00f0ff; | |
| --accent-secondary: #7000ff; | |
| --text-main: #f4f4f5; | |
| --text-muted: #a1a1aa; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --error: #ef4444; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: 'Outfit', sans-serif; | |
| background-color: var(--bg-base); | |
| color: var(--text-main); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Dynamic Background */ | |
| .ambient-bg { | |
| position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; | |
| z-index: -1; overflow: hidden; background: var(--bg-base); | |
| } | |
| .orb { | |
| position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.4; | |
| animation: float 20s infinite alternate cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .orb-1 { top: -10%; left: -10%; width: 50vw; height: 50vw; background: radial-gradient(circle, var(--accent-secondary) 0%, transparent 70%); animation-delay: -5s; } | |
| .orb-2 { bottom: -20%; right: -10%; width: 60vw; height: 60vw; background: radial-gradient(circle, var(--accent-primary) 0%, transparent 70%); } | |
| @keyframes float { 0% { transform: translate(0, 0) scale(1); } 100% { transform: translate(10%, 15%) scale(1.2); } } | |
| /* Layout */ | |
| .app-container { | |
| max-width: 1500px; margin: 0 auto; padding: 2rem; | |
| flex: 1; display: flex; flex-direction: column; gap: 2rem; width: 100%; | |
| } | |
| /* Header */ | |
| header { | |
| display: flex; justify-content: space-between; align-items: center; | |
| padding: 1.5rem 2rem; background: var(--bg-surface); | |
| backdrop-filter: blur(20px); border: 1px solid var(--glass-border); | |
| border-radius: 24px; box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| } | |
| .logo-area { display: flex; align-items: center; gap: 1rem; } | |
| .logo-icon { | |
| width: 48px; height: 48px; background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); | |
| border-radius: 14px; display: flex; align-items: center; justify-content: center; | |
| box-shadow: 0 0 20px rgba(112, 0, 255, 0.4); | |
| } | |
| .logo-icon svg { width: 28px; height: 28px; color: white; } | |
| .title { font-size: 1.8rem; font-weight: 800; background: linear-gradient(to right, #fff, #a1a1aa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } | |
| .status-pill { | |
| display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; | |
| background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.2); | |
| border-radius: 99px; font-size: 0.9rem; font-weight: 600; color: var(--success); | |
| } | |
| .pulse-dot { width: 8px; height: 8px; background: var(--success); border-radius: 50%; animation: ping 1.5s infinite; } | |
| @keyframes ping { 75%, 100% { transform: scale(2.5); opacity: 0; } } | |
| /* Tabs Nav */ | |
| .tabs { | |
| display: flex; gap: 1rem; margin-bottom: 1rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1rem; | |
| } | |
| .tab-btn { | |
| background: transparent; color: var(--text-muted); border: none; font-size: 1.1rem; | |
| font-family: 'Outfit'; font-weight: 600; cursor: pointer; padding: 0.5rem 1rem; | |
| border-radius: 8px; transition: 0.2s; | |
| } | |
| .tab-btn:hover { color: #fff; background: rgba(255,255,255,0.05); } | |
| .tab-btn.active { color: var(--accent-primary); background: rgba(0, 240, 255, 0.1); } | |
| /* Main Grid */ | |
| .dashboard-grid { | |
| display: grid; grid-template-columns: 1fr 400px; gap: 2rem; height: calc(100vh - 250px); min-height: 600px; | |
| } | |
| .tab-content { display: none; flex-direction: column; gap: 1.5rem; overflow-y: auto; padding-right: 10px;} | |
| .tab-content.active { display: flex; } | |
| .tab-content::-webkit-scrollbar { width: 6px; } | |
| .tab-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; } | |
| /* Panels */ | |
| .panel { | |
| background: var(--bg-surface); backdrop-filter: blur(20px); | |
| border: 1px solid var(--glass-border); border-radius: 24px; padding: 1.5rem; | |
| display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| } | |
| .panel-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--glass-border); justify-content: space-between;} | |
| .panel-title { font-size: 1.1rem; font-weight: 700; color: #fff; text-transform: uppercase; letter-spacing: 1px; display: flex; align-items: center; gap: 0.5rem;} | |
| /* Stats & Overview */ | |
| .stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } | |
| .stat-box { background: rgba(0,0,0,0.3); border: 1px solid var(--glass-border); border-radius: 16px; padding: 1.25rem; border-left: 4px solid var(--accent-primary); } | |
| .stat-box:nth-child(2) { border-left-color: var(--accent-secondary); } | |
| .stat-box:nth-child(3) { border-left-color: var(--success); } | |
| .stat-label { font-size: 0.85rem; color: var(--text-muted); text-transform: uppercase; } | |
| .stat-value { font-size: 2.5rem; font-weight: 800; color: #fff; line-height: 1.2; } | |
| .chart-container { height: 250px; width: 100%; position: relative; } | |
| .post-card { background: rgba(0,0,0,0.2); border: 1px solid var(--glass-border); border-radius: 12px; padding: 1rem; margin-bottom: 0.75rem;} | |
| .post-text { color: #e4e4e7; margin-bottom: 0.5rem; font-size: 0.9rem; } | |
| .post-metrics { display: flex; gap: 1rem; font-size: 0.8rem; color: var(--accent-primary); font-weight: 600; } | |
| .metric-badge { background: rgba(0, 240, 255, 0.1); padding: 2px 8px; border-radius: 4px; } | |
| /* Forms & Inputs */ | |
| .form-group { margin-bottom: 1rem; } | |
| .form-label { display: block; margin-bottom: 0.5rem; color: #e4e4e7; font-size: 0.9rem; font-weight: 500; } | |
| .form-input { | |
| width: 100%; padding: 0.75rem 1rem; background: rgba(0,0,0,0.4); border: 1px solid var(--glass-border); | |
| border-radius: 8px; color: #fff; font-family: 'Outfit'; font-size: 1rem; transition: border 0.2s; | |
| } | |
| .form-input:focus { outline: none; border-color: var(--accent-primary); } | |
| textarea.form-input { min-height: 250px; font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; resize: vertical; } | |
| .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } | |
| .btn-save { | |
| background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); | |
| color: #fff; border: none; padding: 0.75rem 2rem; border-radius: 8px; | |
| font-family: 'Outfit'; font-weight: 700; font-size: 1rem; cursor: pointer; | |
| box-shadow: 0 4px 15px rgba(112,0,255,0.3); transition: transform 0.2s; | |
| } | |
| .btn-save:hover { transform: translateY(-2px); } | |
| .toast { | |
| position: fixed; bottom: 20px; right: 20px; background: var(--success); color: #000; | |
| padding: 1rem 2rem; border-radius: 8px; font-weight: 700; font-size: 1rem; | |
| box-shadow: 0 4px 20px rgba(16,185,129,0.4); transform: translateY(150%); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| z-index: 1000; | |
| } | |
| .toast.show { transform: translateY(0); } | |
| /* Terminal */ | |
| .terminal-panel { height: 100%; } | |
| .terminal-window { | |
| flex: 1; background: #09090b; border-radius: 12px; padding: 1rem; | |
| font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: #d4d4d8; | |
| overflow-y: auto; border: 1px solid #27272a; line-height: 1.6; | |
| } | |
| .log-time { color: #52525b; margin-right: 8px; } | |
| .log-error { color: var(--error); font-weight: 700; } | |
| .log-success { color: var(--success); font-weight: 700; } | |
| .log-warning { color: var(--warning); } | |
| .log-info { color: var(--accent-primary); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="ambient-bg"> | |
| <div class="orb orb-1"></div> | |
| <div class="orb orb-2"></div> | |
| </div> | |
| <div class="app-container"> | |
| <header> | |
| <div class="logo-area"> | |
| <div class="logo-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg></div> | |
| <div class="title">Threads Automation Agent</div> | |
| </div> | |
| <div class="status-pill"> | |
| <div class="pulse-dot"></div> System Online | |
| </div> | |
| </header> | |
| <div class="tabs"> | |
| <button class="tab-btn active" onclick="switchTab('overview')">📊 Overview & Analytics</button> | |
| <button class="tab-btn" onclick="switchTab('settings')">⚙️ Settings & Limits</button> | |
| <button class="tab-btn" onclick="switchTab('prompts')">🧠 Prompt Engineering</button> | |
| </div> | |
| <div class="dashboard-grid"> | |
| <!-- Left Column: Tabs Content --> | |
| <div style="display: flex; flex-direction: column; overflow: hidden; height: 100%;"> | |
| <!-- TAB: OVERVIEW --> | |
| <div id="tab-overview" class="tab-content active"> | |
| <div class="stats-row"> | |
| <div class="stat-box"><div class="stat-label">Total Posts</div><div class="stat-value" id="val-posts">0</div></div> | |
| <div class="stat-box"><div class="stat-label">Total Likes</div><div class="stat-value" id="val-likes">0</div></div> | |
| <div class="stat-box"><div class="stat-label">Total Replies</div><div class="stat-value" id="val-replies">0</div></div> | |
| </div> | |
| <div class="panel"> | |
| <div class="panel-header"><div class="panel-title">📈 Engagement Analytics</div></div> | |
| <div class="chart-container"> | |
| <canvas id="analyticsChart"></canvas> | |
| </div> | |
| </div> | |
| <div class="panel"> | |
| <div class="panel-header"><div class="panel-title">⭐ Top Performing Posts</div></div> | |
| <div id="top-posts-container"> | |
| <div style="color:var(--text-muted); text-align:center; padding: 1rem 0;">Awaiting data...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TAB: SETTINGS --> | |
| <div id="tab-settings" class="tab-content"> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <div class="panel-title">⚙️ Bot Configuration</div> | |
| <button class="btn-save" onclick="saveSettings()">Save Changes</button> | |
| </div> | |
| <h3 style="margin-bottom: 1rem; color: var(--accent-primary);">Schedule Slots (Kyiv Time)</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label class="form-label">Morning Slot Start</label> | |
| <input type="time" class="form-input" id="set_morning_start"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Morning Slot End</label> | |
| <input type="time" class="form-input" id="set_morning_end"> | |
| </div> | |
| </div> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label class="form-label">Evening Slot Start</label> | |
| <input type="time" class="form-input" id="set_evening_start"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Evening Slot End</label> | |
| <input type="time" class="form-input" id="set_evening_end"> | |
| </div> | |
| </div> | |
| <h3 style="margin: 1.5rem 0 1rem 0; color: var(--accent-primary);">Daily Limits & Content</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label class="form-label">Daily Single Posts Limit</label> | |
| <input type="number" class="form-input" id="set_single_limit" min="0" max="10"> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Daily Carousels Limit</label> | |
| <input type="number" class="form-input" id="set_carousel_limit" min="0" max="10"> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Slides per Carousel</label> | |
| <input type="number" class="form-input" id="set_slide_count" min="2" max="10"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TAB: PROMPTS --> | |
| <div id="tab-prompts" class="tab-content"> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <div class="panel-title">🧠 AI Prompt Editor</div> | |
| <button class="btn-save" onclick="saveSettings()">Save Prompts</button> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Single Post Generation Prompt</label> | |
| <textarea class="form-input" id="set_prompt_single"></textarea> | |
| <small style="color:var(--text-muted); display:block; margin-top:5px;">Available vars: {news_context}, {exclude_str}, {tone}</small> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Carousel Generation Prompt</label> | |
| <textarea class="form-input" id="set_prompt_carousel"></textarea> | |
| <small style="color:var(--text-muted); display:block; margin-top:5px;">Available vars: {news_context}, {exclude_str}, {tone}, {slide_count}</small> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Viral Predictor AI Prompt</label> | |
| <textarea class="form-input" id="set_prompt_predictor"></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Terminal --> | |
| <div class="panel terminal-panel"> | |
| <div class="panel-header"> | |
| <div class="panel-title">Terminal Logs</div> | |
| </div> | |
| <div class="terminal-window" id="terminal-window"> | |
| <div style="color: #a1a1aa;">> Initiating connection...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="toast" id="toast">Settings Saved Successfully!</div> | |
| <script> | |
| let myChart = null; | |
| function switchTab(tabId) { | |
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); | |
| document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| document.getElementById('tab-' + tabId).classList.add('active'); | |
| } | |
| function showToast() { | |
| const t = document.getElementById('toast'); | |
| t.classList.add('show'); | |
| setTimeout(() => t.classList.remove('show'), 3000); | |
| } | |
| async function loadSettings() { | |
| try { | |
| const res = await fetch('/api/settings'); | |
| const data = await res.json(); | |
| document.getElementById('set_morning_start').value = data.MORNING_SLOT_START; | |
| document.getElementById('set_morning_end').value = data.MORNING_SLOT_END; | |
| document.getElementById('set_evening_start').value = data.EVENING_SLOT_START; | |
| document.getElementById('set_evening_end').value = data.EVENING_SLOT_END; | |
| document.getElementById('set_single_limit').value = data.DAILY_SINGLE_POSTS_LIMIT; | |
| document.getElementById('set_carousel_limit').value = data.DAILY_CAROUSELS_LIMIT; | |
| document.getElementById('set_slide_count').value = data.CAROUSEL_SLIDE_COUNT; | |
| document.getElementById('set_prompt_single').value = data.PROMPT_SINGLE; | |
| document.getElementById('set_prompt_carousel').value = data.PROMPT_CAROUSEL; | |
| document.getElementById('set_prompt_predictor').value = data.PROMPT_PREDICTOR; | |
| } catch (err) { console.error("Error loading settings:", err); } | |
| } | |
| async function saveSettings() { | |
| const data = { | |
| MORNING_SLOT_START: document.getElementById('set_morning_start').value, | |
| MORNING_SLOT_END: document.getElementById('set_morning_end').value, | |
| EVENING_SLOT_START: document.getElementById('set_evening_start').value, | |
| EVENING_SLOT_END: document.getElementById('set_evening_end').value, | |
| DAILY_SINGLE_POSTS_LIMIT: parseInt(document.getElementById('set_single_limit').value), | |
| DAILY_CAROUSELS_LIMIT: parseInt(document.getElementById('set_carousel_limit').value), | |
| CAROUSEL_SLIDE_COUNT: parseInt(document.getElementById('set_slide_count').value), | |
| PROMPT_SINGLE: document.getElementById('set_prompt_single').value, | |
| PROMPT_CAROUSEL: document.getElementById('set_prompt_carousel').value, | |
| PROMPT_PREDICTOR: document.getElementById('set_prompt_predictor').value, | |
| }; | |
| try { | |
| const res = await fetch('/api/save_settings', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data) | |
| }); | |
| if(res.ok) showToast(); | |
| } catch (err) { alert("Error saving settings."); } | |
| } | |
| // Analytics Chart Setup | |
| function initChart(chartData) { | |
| const ctx = document.getElementById('analyticsChart').getContext('2d'); | |
| // Dummy historical data simulation if not enough data | |
| const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; | |
| const likesData = [0,0,0,0,0,0, chartData.total_likes]; | |
| const repliesData = [0,0,0,0,0,0, chartData.total_replies]; | |
| if(myChart) myChart.destroy(); | |
| myChart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: labels, | |
| datasets: [ | |
| { | |
| label: 'Likes', | |
| data: likesData, | |
| borderColor: '#00f0ff', | |
| backgroundColor: 'rgba(0, 240, 255, 0.1)', | |
| borderWidth: 2, | |
| fill: true, | |
| tension: 0.4 | |
| }, | |
| { | |
| label: 'Comments', | |
| data: repliesData, | |
| borderColor: '#7000ff', | |
| backgroundColor: 'rgba(112, 0, 255, 0.1)', | |
| borderWidth: 2, | |
| fill: true, | |
| tension: 0.4 | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { labels: { color: '#a1a1aa' } } }, | |
| scales: { | |
| y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#a1a1aa' } }, | |
| x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#a1a1aa' } } | |
| } | |
| } | |
| }); | |
| } | |
| // Terminal & Stats Logic | |
| function colorizeLog(line) { | |
| if (line.includes('[ERROR]') || line.includes('Exception') || line.includes('Error')) return `<span class="log-error">${line}</span>`; | |
| if (line.includes('SUCCESS') || line.includes('одобрен') || line.includes('успешно')) return `<span class="log-success">${line}</span>`; | |
| if (line.includes('Predictor') || line.includes('[Auto-Improvement]')) return `<span class="log-warning">${line}</span>`; | |
| if (line.includes('Scheduler') || line.includes('Keep-Alive') || line.includes('Content Generator')) return `<span class="log-info">${line}</span>`; | |
| return line.replace(/^(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\])(.*)/, '<span class="log-time">$1</span>$2'); | |
| } | |
| let lastLogs = ""; | |
| async function fetchLogs() { | |
| try { | |
| const res = await fetch('/api/logs'); | |
| const data = await res.json(); | |
| const term = document.getElementById('terminal-window'); | |
| if (data.logs !== lastLogs) { | |
| const isScrolledToBottom = term.scrollHeight - term.clientHeight <= term.scrollTop + 5; | |
| term.innerHTML = data.logs.split('\n').filter(l => l.trim() !== '').map(l => colorizeLog(l)).join('<br>') || '<span style="color:#a1a1aa">> No logs...</span>'; | |
| lastLogs = data.logs; | |
| if (isScrolledToBottom) term.scrollTop = term.scrollHeight; | |
| } | |
| } catch(e) {} | |
| } | |
| async function fetchStats() { | |
| try { | |
| const res = await fetch('/api/stats'); | |
| const data = await res.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 tc = document.getElementById('top-posts-container'); | |
| if (data.top_posts && data.top_posts.length > 0) { | |
| tc.innerHTML = data.top_posts.map(post => ` | |
| <div class="post-card"> | |
| <div class="post-text">${post.text.length > 120 ? post.text.substring(0,120)+'...' : post.text}</div> | |
| <div class="post-metrics"> | |
| <span class="metric-badge">❤️ ${post.likes || 0}</span> | |
| <span class="metric-badge">💬 ${post.replies || 0}</span> | |
| ${post.predicted_score ? `<span class="metric-badge">Score: ${post.predicted_score}/10</span>` : ''} | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| if(!myChart) initChart(data); | |
| } catch(e) {} | |
| } | |
| // Init | |
| loadSettings(); | |
| fetchLogs(); | |
| fetchStats(); | |
| setInterval(fetchLogs, 2000); | |
| setInterval(fetchStats, 10000); | |
| </script> | |
| </body> | |
| </html> | |