|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Crypto API Monitor - Real Data Dashboard</title> |
|
|
<style> |
|
|
* { margin: 0; padding: 0; box-sizing: border-box; } |
|
|
|
|
|
@keyframes gradientShift { |
|
|
0% { background-position: 0% 50%; } |
|
|
50% { background-position: 100% 50%; } |
|
|
100% { background-position: 0% 50%; } |
|
|
} |
|
|
|
|
|
@keyframes fadeInUp { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(30px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0%, 100% { transform: scale(1); } |
|
|
50% { transform: scale(1.05); } |
|
|
} |
|
|
|
|
|
@keyframes shimmer { |
|
|
0% { background-position: -1000px 0; } |
|
|
100% { background-position: 1000px 0; } |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
background: linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #4facfe); |
|
|
background-size: 400% 400%; |
|
|
animation: gradientShift 15s ease infinite; |
|
|
padding: 20px; |
|
|
color: #1a1a1a; |
|
|
min-height: 100vh; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1400px; |
|
|
margin: 0 auto; |
|
|
background: rgba(255, 255, 255, 0.95); |
|
|
backdrop-filter: blur(10px); |
|
|
border-radius: 24px; |
|
|
padding: 40px; |
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3); |
|
|
animation: fadeInUp 0.6s ease; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin-bottom: 10px; |
|
|
font-size: 42px; |
|
|
font-weight: 900; |
|
|
letter-spacing: -1px; |
|
|
animation: shimmer 3s infinite linear; |
|
|
background-size: 1000px 100%; |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
color: #6c757d; |
|
|
font-size: 16px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.stats-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); |
|
|
gap: 24px; |
|
|
margin: 30px 0; |
|
|
} |
|
|
|
|
|
.stat-card { |
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); |
|
|
padding: 28px; |
|
|
border-radius: 20px; |
|
|
border: 3px solid transparent; |
|
|
background-clip: padding-box; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.stat-card::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
border-radius: 20px; |
|
|
padding: 3px; |
|
|
background: linear-gradient(135deg, #667eea, #764ba2, #f093fb); |
|
|
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); |
|
|
-webkit-mask-composite: xor; |
|
|
mask-composite: exclude; |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s; |
|
|
} |
|
|
|
|
|
.stat-card:hover { |
|
|
transform: translateY(-8px) scale(1.02); |
|
|
box-shadow: 0 12px 40px rgba(102, 126, 234, 0.3); |
|
|
} |
|
|
|
|
|
.stat-card:hover::before { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.stat-icon { |
|
|
font-size: 32px; |
|
|
margin-bottom: 12px; |
|
|
display: inline-block; |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
.stat-value { |
|
|
font-size: 48px; |
|
|
font-weight: 900; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin: 12px 0; |
|
|
line-height: 1; |
|
|
} |
|
|
|
|
|
.stat-value.green { |
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
|
|
|
.stat-value.red { |
|
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
|
|
|
.stat-value.orange { |
|
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
|
|
|
.stat-label { |
|
|
font-size: 13px; |
|
|
color: #6c757d; |
|
|
text-transform: uppercase; |
|
|
font-weight: 700; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.section-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 12px; |
|
|
margin: 40px 0 20px 0; |
|
|
padding-bottom: 16px; |
|
|
border-bottom: 3px solid; |
|
|
border-image: linear-gradient(90deg, #667eea, #764ba2, transparent) 1; |
|
|
} |
|
|
|
|
|
.section-header h2 { |
|
|
font-size: 28px; |
|
|
font-weight: 800; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
} |
|
|
|
|
|
.providers-table { |
|
|
width: 100%; |
|
|
border-collapse: separate; |
|
|
border-spacing: 0; |
|
|
margin: 20px 0; |
|
|
border-radius: 16px; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.providers-table th { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
padding: 18px; |
|
|
text-align: left; |
|
|
font-weight: 700; |
|
|
text-transform: uppercase; |
|
|
font-size: 12px; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.providers-table td { |
|
|
padding: 18px; |
|
|
border-bottom: 1px solid #e9ecef; |
|
|
background: white; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.providers-table tr:hover td { |
|
|
background: linear-gradient(90deg, #f8f9fa 0%, #ffffff 100%); |
|
|
transform: scale(1.01); |
|
|
} |
|
|
|
|
|
.providers-table tr:last-child td { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
.status-badge { |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: 6px; |
|
|
padding: 6px 14px; |
|
|
border-radius: 20px; |
|
|
font-size: 12px; |
|
|
font-weight: 700; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.status-badge::before { |
|
|
content: ''; |
|
|
width: 8px; |
|
|
height: 8px; |
|
|
border-radius: 50%; |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
.status-online { |
|
|
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); |
|
|
color: #065f46; |
|
|
border: 2px solid #10b981; |
|
|
} |
|
|
|
|
|
.status-online::before { |
|
|
background: #10b981; |
|
|
box-shadow: 0 0 10px #10b981; |
|
|
} |
|
|
|
|
|
.status-offline { |
|
|
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); |
|
|
color: #991b1b; |
|
|
border: 2px solid #ef4444; |
|
|
} |
|
|
|
|
|
.status-offline::before { |
|
|
background: #ef4444; |
|
|
box-shadow: 0 0 10px #ef4444; |
|
|
} |
|
|
|
|
|
.status-degraded { |
|
|
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); |
|
|
color: #92400e; |
|
|
border: 2px solid #f59e0b; |
|
|
} |
|
|
|
|
|
.status-degraded::before { |
|
|
background: #f59e0b; |
|
|
box-shadow: 0 0 10px #f59e0b; |
|
|
} |
|
|
|
|
|
.refresh-btn { |
|
|
padding: 14px 28px; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white; |
|
|
border: none; |
|
|
border-radius: 12px; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
margin: 10px 5px; |
|
|
font-size: 14px; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.refresh-btn::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
width: 0; |
|
|
height: 0; |
|
|
border-radius: 50%; |
|
|
background: rgba(255,255,255,0.3); |
|
|
transform: translate(-50%, -50%); |
|
|
transition: width 0.6s, height 0.6s; |
|
|
} |
|
|
|
|
|
.refresh-btn:hover::before { |
|
|
width: 300px; |
|
|
height: 300px; |
|
|
} |
|
|
|
|
|
.refresh-btn:hover { |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); |
|
|
} |
|
|
|
|
|
.refresh-btn:active { |
|
|
transform: translateY(-1px); |
|
|
} |
|
|
|
|
|
.last-update { |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
color: #6c757d; |
|
|
font-size: 14px; |
|
|
margin: 10px 0; |
|
|
padding: 8px 16px; |
|
|
background: #f8f9fa; |
|
|
border-radius: 20px; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.hf-section { |
|
|
margin-top: 40px; |
|
|
padding: 32px; |
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
|
|
border-radius: 20px; |
|
|
border: 3px solid #dee2e6; |
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
textarea { |
|
|
width: 100%; |
|
|
padding: 16px; |
|
|
border: 3px solid #dee2e6; |
|
|
border-radius: 12px; |
|
|
font-family: 'Consolas', 'Monaco', monospace; |
|
|
margin: 16px 0; |
|
|
font-size: 14px; |
|
|
transition: all 0.3s; |
|
|
background: white; |
|
|
} |
|
|
|
|
|
textarea:focus { |
|
|
outline: none; |
|
|
border-color: #667eea; |
|
|
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); |
|
|
} |
|
|
|
|
|
.sentiment-result { |
|
|
font-size: 56px; |
|
|
font-weight: 900; |
|
|
padding: 32px; |
|
|
background: white; |
|
|
border-radius: 16px; |
|
|
text-align: center; |
|
|
margin: 16px 0; |
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.08); |
|
|
border: 3px solid #dee2e6; |
|
|
min-height: 120px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
pre { |
|
|
background: #1e293b !important; |
|
|
color: #e2e8f0 !important; |
|
|
padding: 20px !important; |
|
|
border-radius: 12px !important; |
|
|
overflow-x: auto !important; |
|
|
font-size: 13px !important; |
|
|
line-height: 1.6 !important; |
|
|
box-shadow: inset 0 2px 8px rgba(0,0,0,0.3) !important; |
|
|
} |
|
|
|
|
|
.loading { |
|
|
display: inline-block; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
border: 3px solid #f3f4f6; |
|
|
border-top-color: #667eea; |
|
|
border-radius: 50%; |
|
|
animation: spin 0.8s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
.response-time { |
|
|
font-weight: 700; |
|
|
padding: 4px 10px; |
|
|
border-radius: 8px; |
|
|
font-size: 13px; |
|
|
} |
|
|
|
|
|
.response-fast { |
|
|
background: #d1fae5; |
|
|
color: #065f46; |
|
|
} |
|
|
|
|
|
.response-medium { |
|
|
background: #fef3c7; |
|
|
color: #92400e; |
|
|
} |
|
|
|
|
|
.response-slow { |
|
|
background: #fee2e2; |
|
|
color: #991b1b; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>🚀 Crypto API Monitor</h1> |
|
|
<p class="subtitle">Real-time monitoring of cryptocurrency APIs with live data</p> |
|
|
<p class="last-update">⏱️ Last Update: <span id="lastUpdate">Loading...</span></p> |
|
|
|
|
|
<div style="margin: 20px 0;"> |
|
|
<button class="refresh-btn" onclick="loadData()">🔄 Refresh Data</button> |
|
|
<button class="refresh-btn" onclick="window.location.href='/hf_console.html'">🤗 HF Console</button> |
|
|
<button class="refresh-btn" onclick="window.location.href='/admin.html'">⚙️ Admin Panel</button> |
|
|
<button class="refresh-btn" onclick="window.location.href='/index.html'">📊 Full Dashboard</button> |
|
|
</div> |
|
|
|
|
|
<div class="stats-grid"> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-icon">📡</div> |
|
|
<div class="stat-label">Total APIs</div> |
|
|
<div class="stat-value" id="totalAPIs">0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-icon">✅</div> |
|
|
<div class="stat-label">Online</div> |
|
|
<div class="stat-value green" id="onlineAPIs">0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-icon">❌</div> |
|
|
<div class="stat-label">Offline</div> |
|
|
<div class="stat-value red" id="offlineAPIs">0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div class="stat-icon">⚡</div> |
|
|
<div class="stat-label">Avg Response</div> |
|
|
<div class="stat-value orange" id="avgResponse" style="font-size: 32px;">0ms</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="section-header"> |
|
|
<h2>📊 API Providers Status</h2> |
|
|
</div> |
|
|
<table class="providers-table"> |
|
|
<thead> |
|
|
<tr> |
|
|
<th>Provider</th> |
|
|
<th>Category</th> |
|
|
<th>Status</th> |
|
|
<th>Response Time</th> |
|
|
<th>Last Check</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody id="providersTable"> |
|
|
<tr><td colspan="5" style="text-align: center;">Loading...</td></tr> |
|
|
</tbody> |
|
|
</table> |
|
|
|
|
|
<div class="hf-section"> |
|
|
<div class="section-header" style="border: none; margin: 0 0 20px 0;"> |
|
|
<h2>🤗 HuggingFace Sentiment Analysis</h2> |
|
|
</div> |
|
|
<p style="color: #6c757d; margin-bottom: 10px;">Enter crypto-related text (one per line) to analyze sentiment using AI:</p> |
|
|
<textarea id="sentimentText" rows="5" placeholder="BTC strong breakout ETH looks weak Market is bullish">BTC strong breakout |
|
|
ETH looks weak |
|
|
Market is bullish today</textarea> |
|
|
<button class="refresh-btn" onclick="runSentiment()">🧠 Analyze Sentiment</button> |
|
|
<div class="sentiment-result" id="sentimentResult">—</div> |
|
|
<pre id="sentimentDetails" style="background: white; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 12px;"></pre> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
async function loadData() { |
|
|
try { |
|
|
|
|
|
const tbody = document.getElementById('providersTable'); |
|
|
if (tbody) { |
|
|
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;"><div class="loading"></div><div style="margin-top: 10px; color: #6c757d;">در حال بارگذاری...</div></td></tr>'; |
|
|
} |
|
|
if (document.getElementById('lastUpdate')) { |
|
|
document.getElementById('lastUpdate').textContent = 'در حال بارگذاری...'; |
|
|
} |
|
|
|
|
|
|
|
|
const statusRes = await fetch('/api/status'); |
|
|
if (!statusRes.ok) { |
|
|
throw new Error(`خطا در دریافت وضعیت: ${statusRes.status} ${statusRes.statusText}`); |
|
|
} |
|
|
const status = await statusRes.json(); |
|
|
|
|
|
if (!status || typeof status.total_providers === 'undefined') { |
|
|
throw new Error('دادههای وضعیت نامعتبر است'); |
|
|
} |
|
|
|
|
|
if (document.getElementById('totalAPIs')) { |
|
|
document.getElementById('totalAPIs').textContent = status.total_providers || 0; |
|
|
} |
|
|
if (document.getElementById('onlineAPIs')) { |
|
|
document.getElementById('onlineAPIs').textContent = status.online || 0; |
|
|
} |
|
|
if (document.getElementById('offlineAPIs')) { |
|
|
document.getElementById('offlineAPIs').textContent = status.offline || 0; |
|
|
} |
|
|
if (document.getElementById('avgResponse')) { |
|
|
document.getElementById('avgResponse').textContent = (status.avg_response_time_ms || 0) + 'ms'; |
|
|
} |
|
|
if (document.getElementById('lastUpdate')) { |
|
|
document.getElementById('lastUpdate').textContent = status.timestamp ? new Date(status.timestamp).toLocaleString('fa-IR') : 'نامشخص'; |
|
|
} |
|
|
|
|
|
|
|
|
const providersRes = await fetch('/api/providers'); |
|
|
if (!providersRes.ok) { |
|
|
throw new Error(`خطا در دریافت لیست APIها: ${providersRes.status} ${providersRes.statusText}`); |
|
|
} |
|
|
const providers = await providersRes.json(); |
|
|
|
|
|
if (!providers || !Array.isArray(providers)) { |
|
|
throw new Error('لیست APIها نامعتبر است'); |
|
|
} |
|
|
|
|
|
if (tbody) { |
|
|
if (providers.length === 0) { |
|
|
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px; color: #6c757d;">هیچ APIای یافت نشد</td></tr>'; |
|
|
} else { |
|
|
tbody.innerHTML = providers.map(p => { |
|
|
let responseClass = 'response-fast'; |
|
|
const responseTime = p.response_time_ms || p.avg_response_time_ms || 0; |
|
|
if (responseTime > 3000) responseClass = 'response-slow'; |
|
|
else if (responseTime > 1000) responseClass = 'response-medium'; |
|
|
|
|
|
return ` |
|
|
<tr> |
|
|
<td><strong style="font-size: 15px;">${p.name || 'نامشخص'}</strong></td> |
|
|
<td><span style="background: #f8f9fa; padding: 4px 10px; border-radius: 8px; font-size: 12px; font-weight: 600;">${p.category || 'نامشخص'}</span></td> |
|
|
<td><span class="status-badge status-${p.status || 'unknown'}">${(p.status || 'unknown').toUpperCase()}</span></td> |
|
|
<td><span class="response-time ${responseClass}">${responseTime}ms</span></td> |
|
|
<td style="color: #6c757d; font-size: 13px;">${p.last_fetch ? new Date(p.last_fetch).toLocaleTimeString('fa-IR') : 'نامشخص'}</td> |
|
|
</tr> |
|
|
`}).join(''); |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error loading data:', error); |
|
|
const tbody = document.getElementById('providersTable'); |
|
|
if (tbody) { |
|
|
tbody.innerHTML = `<tr><td colspan="5" style="text-align: center; padding: 40px; color: #ef4444;"> |
|
|
<div style="font-size: 24px; margin-bottom: 10px;">❌</div> |
|
|
<div style="font-weight: 600; margin-bottom: 5px;">خطا در بارگذاری دادهها</div> |
|
|
<div style="font-size: 14px; color: #6c757d; margin-bottom: 15px;">${error.message || 'خطای نامشخص'}</div> |
|
|
<button onclick="loadData()" style="padding: 10px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 12px; color: white; cursor: pointer; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">تلاش مجدد</button> |
|
|
</td></tr>`; |
|
|
} |
|
|
if (document.getElementById('lastUpdate')) { |
|
|
document.getElementById('lastUpdate').textContent = 'خطا در بارگذاری'; |
|
|
} |
|
|
alert('❌ خطا در بارگذاری دادهها:\n' + (error.message || 'خطای نامشخص')); |
|
|
} |
|
|
} |
|
|
|
|
|
async function runSentiment() { |
|
|
const text = document.getElementById('sentimentText').value; |
|
|
const texts = text.split('\n').filter(t => t.trim()); |
|
|
|
|
|
if (texts.length === 0) { |
|
|
alert('Please enter at least one line of text'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
document.getElementById('sentimentResult').textContent = '⏳ Analyzing...'; |
|
|
document.getElementById('sentimentDetails').textContent = ''; |
|
|
|
|
|
const res = await fetch('/api/hf/run-sentiment', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ texts }) |
|
|
}); |
|
|
|
|
|
if (!res.ok) { |
|
|
throw new Error(`HTTP ${res.status}: ${await res.text()}`); |
|
|
} |
|
|
|
|
|
const data = await res.json(); |
|
|
|
|
|
const vote = data.vote || 0; |
|
|
let emoji = '😐'; |
|
|
let color = '#6c757d'; |
|
|
|
|
|
if (vote > 0.2) { |
|
|
emoji = '📈'; |
|
|
color = '#10b981'; |
|
|
} else if (vote < -0.2) { |
|
|
emoji = '📉'; |
|
|
color = '#ef4444'; |
|
|
} |
|
|
|
|
|
document.getElementById('sentimentResult').innerHTML = ` |
|
|
<span style="color: ${color};">${emoji} ${vote.toFixed(3)}</span> |
|
|
`; |
|
|
document.getElementById('sentimentDetails').textContent = JSON.stringify(data, null, 2); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error running sentiment:', error); |
|
|
document.getElementById('sentimentResult').innerHTML = '<span style="color: #ef4444;">❌ Error</span>'; |
|
|
document.getElementById('sentimentDetails').textContent = 'Error: ' + error.message; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
loadData(); |
|
|
|
|
|
|
|
|
setInterval(loadData, 30000); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|