Really-amin commited on
Commit
40f032f
·
verified ·
1 Parent(s): 93ece91

Upload 322 files

Browse files
.gitattributes CHANGED
@@ -36,3 +36,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
36
  data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
37
  hf-data-engine/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
38
  app/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
 
 
36
  data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
37
  hf-data-engine/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
38
  app/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
39
+ __pycache__/hf_unified_server.cpython-313.pyc filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -1,51 +1,24 @@
1
- FROM python:3.10-slim
2
 
3
  WORKDIR /app
4
 
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- git \
9
- curl \
10
- && rm -rf /var/lib/apt/lists/*
11
 
12
- # Copy requirements first for better caching
13
  COPY requirements.txt .
14
-
15
- # Upgrade pip
16
- RUN pip install --no-cache-dir --upgrade pip
17
-
18
- # Install dependencies
19
  RUN pip install --no-cache-dir -r requirements.txt
20
 
21
  # Copy application code
22
  COPY . .
23
 
24
- # Create necessary directories
25
- RUN mkdir -p \
26
- data/database \
27
- data/backups \
28
- logs \
29
- static/css \
30
- static/js \
31
- .cache/huggingface
32
-
33
- # Set permissions
34
- RUN chmod -R 755 /app
35
-
36
- # Environment variables
37
- ENV PORT=7860 \
38
- PYTHONUNBUFFERED=1 \
39
- TRANSFORMERS_CACHE=/app/.cache/huggingface \
40
- HF_HOME=/app/.cache/huggingface \
41
- PYTHONDONTWRITEBYTECODE=1
42
 
43
  # Expose port
44
  EXPOSE 7860
45
 
46
- # Health check
47
- HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
48
- CMD curl -f http://localhost:7860/api/health || exit 1
49
-
50
- # Run application
51
- CMD ["uvicorn", "hf_unified_server:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
 
1
+ FROM python:3.10
2
 
3
  WORKDIR /app
4
 
5
+ # Create required directories
6
+ RUN mkdir -p /app/logs /app/data /app/data/database /app/data/backups
 
 
 
 
7
 
8
+ # Copy requirements and install dependencies
9
  COPY requirements.txt .
 
 
 
 
 
10
  RUN pip install --no-cache-dir -r requirements.txt
11
 
12
  # Copy application code
13
  COPY . .
14
 
15
+ # Set environment variables
16
+ ENV USE_MOCK_DATA=false
17
+ ENV PORT=7860
18
+ ENV PYTHONUNBUFFERED=1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  # Expose port
21
  EXPOSE 7860
22
 
23
+ # Launch command
24
+ CMD ["uvicorn", "api_server_extended:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
__pycache__/ai_models.cpython-313.pyc CHANGED
Binary files a/__pycache__/ai_models.cpython-313.pyc and b/__pycache__/ai_models.cpython-313.pyc differ
 
__pycache__/config.cpython-313.pyc CHANGED
Binary files a/__pycache__/config.cpython-313.pyc and b/__pycache__/config.cpython-313.pyc differ
 
__pycache__/hf_unified_server.cpython-313.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:95d175b459a983f328419804aacfc0f73321bc4e516957dfabc266847afc655c
3
+ size 113609
__pycache__/main.cpython-313.pyc ADDED
Binary file (1.46 kB). View file
 
__pycache__/simple_server.cpython-313.pyc ADDED
Binary file (29.1 kB). View file
 
dashboard.html CHANGED
@@ -1,638 +1,113 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Crypto API Monitor - Real Data Dashboard</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
-
10
- @keyframes gradientShift {
11
- 0% { background-position: 0% 50%; }
12
- 50% { background-position: 100% 50%; }
13
- 100% { background-position: 0% 50%; }
14
- }
15
-
16
- @keyframes fadeInUp {
17
- from {
18
- opacity: 0;
19
- transform: translateY(30px);
20
- }
21
- to {
22
- opacity: 1;
23
- transform: translateY(0);
24
- }
25
- }
26
-
27
- @keyframes pulse {
28
- 0%, 100% { transform: scale(1); }
29
- 50% { transform: scale(1.05); }
30
- }
31
-
32
- @keyframes shimmer {
33
- 0% { background-position: -1000px 0; }
34
- 100% { background-position: 1000px 0; }
35
- }
36
-
37
- body {
38
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
39
- background: linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #4facfe);
40
- background-size: 400% 400%;
41
- animation: gradientShift 15s ease infinite;
42
- padding: 20px;
43
- color: #1a1a1a;
44
- min-height: 100vh;
45
- }
46
-
47
- .container {
48
- max-width: 1400px;
49
- margin: 0 auto;
50
- background: rgba(255, 255, 255, 0.95);
51
- backdrop-filter: blur(10px);
52
- border-radius: 24px;
53
- padding: 40px;
54
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
55
- animation: fadeInUp 0.6s ease;
56
- }
57
-
58
- h1 {
59
- background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
60
- -webkit-background-clip: text;
61
- -webkit-text-fill-color: transparent;
62
- background-clip: text;
63
- margin-bottom: 10px;
64
- font-size: 42px;
65
- font-weight: 900;
66
- letter-spacing: -1px;
67
- animation: shimmer 3s infinite linear;
68
- background-size: 1000px 100%;
69
- }
70
-
71
- .subtitle {
72
- color: #6c757d;
73
- font-size: 16px;
74
- margin-bottom: 20px;
75
- }
76
-
77
- .stats-grid {
78
- display: grid;
79
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
80
- gap: 24px;
81
- margin: 30px 0;
82
- }
83
-
84
- .stat-card {
85
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
86
- padding: 28px;
87
- border-radius: 20px;
88
- border: 3px solid transparent;
89
- background-clip: padding-box;
90
- position: relative;
91
- overflow: hidden;
92
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
93
- box-shadow: 0 4px 20px rgba(0,0,0,0.08);
94
- }
95
-
96
- .stat-card::before {
97
- content: '';
98
- position: absolute;
99
- top: 0;
100
- left: 0;
101
- right: 0;
102
- bottom: 0;
103
- border-radius: 20px;
104
- padding: 3px;
105
- background: linear-gradient(135deg, #667eea, #764ba2, #f093fb);
106
- -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
107
- -webkit-mask-composite: xor;
108
- mask-composite: exclude;
109
- opacity: 0;
110
- transition: opacity 0.3s;
111
- }
112
-
113
- .stat-card:hover {
114
- transform: translateY(-8px) scale(1.02);
115
- box-shadow: 0 12px 40px rgba(102, 126, 234, 0.3);
116
- }
117
-
118
- .stat-card:hover::before {
119
- opacity: 1;
120
- }
121
-
122
- .stat-icon {
123
- font-size: 32px;
124
- margin-bottom: 12px;
125
- display: inline-block;
126
- animation: pulse 2s infinite;
127
- }
128
-
129
- .stat-value {
130
- font-size: 48px;
131
- font-weight: 900;
132
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
133
- -webkit-background-clip: text;
134
- -webkit-text-fill-color: transparent;
135
- background-clip: text;
136
- margin: 12px 0;
137
- line-height: 1;
138
- }
139
-
140
- .stat-value.green {
141
- background: linear-gradient(135deg, #10b981 0%, #059669 100%);
142
- -webkit-background-clip: text;
143
- -webkit-text-fill-color: transparent;
144
- }
145
-
146
- .stat-value.red {
147
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
148
- -webkit-background-clip: text;
149
- -webkit-text-fill-color: transparent;
150
- }
151
-
152
- .stat-value.orange {
153
- background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
154
- -webkit-background-clip: text;
155
- -webkit-text-fill-color: transparent;
156
- }
157
-
158
- .stat-label {
159
- font-size: 13px;
160
- color: #6c757d;
161
- text-transform: uppercase;
162
- font-weight: 700;
163
- letter-spacing: 1px;
164
- }
165
-
166
- .section-header {
167
- display: flex;
168
- align-items: center;
169
- gap: 12px;
170
- margin: 40px 0 20px 0;
171
- padding-bottom: 16px;
172
- border-bottom: 3px solid;
173
- border-image: linear-gradient(90deg, #667eea, #764ba2, transparent) 1;
174
- }
175
-
176
- .section-header h2 {
177
- font-size: 28px;
178
- font-weight: 800;
179
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
180
- -webkit-background-clip: text;
181
- -webkit-text-fill-color: transparent;
182
- }
183
-
184
- .providers-table {
185
- width: 100%;
186
- border-collapse: separate;
187
- border-spacing: 0;
188
- margin: 20px 0;
189
- border-radius: 16px;
190
- overflow: hidden;
191
- box-shadow: 0 4px 20px rgba(0,0,0,0.08);
192
- }
193
-
194
- .providers-table th {
195
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
196
- color: white;
197
- padding: 18px;
198
- text-align: left;
199
- font-weight: 700;
200
- text-transform: uppercase;
201
- font-size: 12px;
202
- letter-spacing: 1px;
203
- }
204
-
205
- .providers-table td {
206
- padding: 18px;
207
- border-bottom: 1px solid #e9ecef;
208
- background: white;
209
- transition: all 0.2s;
210
- }
211
-
212
- .providers-table tr:hover td {
213
- background: linear-gradient(90deg, #f8f9fa 0%, #ffffff 100%);
214
- transform: scale(1.01);
215
- }
216
-
217
- .providers-table tr:last-child td {
218
- border-bottom: none;
219
- }
220
-
221
- .status-badge {
222
- display: inline-flex;
223
- align-items: center;
224
- gap: 6px;
225
- padding: 6px 14px;
226
- border-radius: 20px;
227
- font-size: 12px;
228
- font-weight: 700;
229
- text-transform: uppercase;
230
- letter-spacing: 0.5px;
231
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
232
- }
233
-
234
- .status-badge::before {
235
- content: '';
236
- width: 8px;
237
- height: 8px;
238
- border-radius: 50%;
239
- animation: pulse 2s infinite;
240
- }
241
-
242
- .status-online {
243
- background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
244
- color: #065f46;
245
- border: 2px solid #10b981;
246
- }
247
-
248
- .status-online::before {
249
- background: #10b981;
250
- box-shadow: 0 0 10px #10b981;
251
- }
252
-
253
- .status-offline {
254
- background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
255
- color: #991b1b;
256
- border: 2px solid #ef4444;
257
- }
258
-
259
- .status-offline::before {
260
- background: #ef4444;
261
- box-shadow: 0 0 10px #ef4444;
262
- }
263
-
264
- .status-degraded {
265
- background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
266
- color: #92400e;
267
- border: 2px solid #f59e0b;
268
- }
269
-
270
- .status-degraded::before {
271
- background: #f59e0b;
272
- box-shadow: 0 0 10px #f59e0b;
273
- }
274
-
275
- .refresh-btn {
276
- padding: 14px 28px;
277
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
278
- color: white;
279
- border: none;
280
- border-radius: 12px;
281
- font-weight: 700;
282
- cursor: pointer;
283
- margin: 10px 5px;
284
- font-size: 14px;
285
- text-transform: uppercase;
286
- letter-spacing: 0.5px;
287
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
288
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
289
- position: relative;
290
- overflow: hidden;
291
- }
292
-
293
- .refresh-btn::before {
294
- content: '';
295
- position: absolute;
296
- top: 50%;
297
- left: 50%;
298
- width: 0;
299
- height: 0;
300
- border-radius: 50%;
301
- background: rgba(255,255,255,0.3);
302
- transform: translate(-50%, -50%);
303
- transition: width 0.6s, height 0.6s;
304
- }
305
-
306
- .refresh-btn:hover::before {
307
- width: 300px;
308
- height: 300px;
309
- }
310
-
311
- .refresh-btn:hover {
312
- transform: translateY(-3px);
313
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
314
- }
315
-
316
- .refresh-btn:active {
317
- transform: translateY(-1px);
318
- }
319
-
320
- .last-update {
321
- display: inline-flex;
322
- align-items: center;
323
- gap: 8px;
324
- color: #6c757d;
325
- font-size: 14px;
326
- margin: 10px 0;
327
- padding: 8px 16px;
328
- background: #f8f9fa;
329
- border-radius: 20px;
330
- font-weight: 600;
331
- }
332
-
333
- .hf-section {
334
- margin-top: 40px;
335
- padding: 32px;
336
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
337
- border-radius: 20px;
338
- border: 3px solid #dee2e6;
339
- box-shadow: 0 4px 20px rgba(0,0,0,0.08);
340
- }
341
-
342
- textarea {
343
- width: 100%;
344
- padding: 16px;
345
- border: 3px solid #dee2e6;
346
- border-radius: 12px;
347
- font-family: 'Consolas', 'Monaco', monospace;
348
- margin: 16px 0;
349
- font-size: 14px;
350
- transition: all 0.3s;
351
- background: white;
352
- }
353
-
354
- textarea:focus {
355
- outline: none;
356
- border-color: #667eea;
357
- box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
358
- }
359
-
360
- .sentiment-result {
361
- font-size: 56px;
362
- font-weight: 900;
363
- padding: 32px;
364
- background: white;
365
- border-radius: 16px;
366
- text-align: center;
367
- margin: 16px 0;
368
- box-shadow: 0 4px 20px rgba(0,0,0,0.08);
369
- border: 3px solid #dee2e6;
370
- min-height: 120px;
371
- display: flex;
372
- align-items: center;
373
- justify-content: center;
374
- }
375
-
376
- pre {
377
- background: #1e293b !important;
378
- color: #e2e8f0 !important;
379
- padding: 20px !important;
380
- border-radius: 12px !important;
381
- overflow-x: auto !important;
382
- font-size: 13px !important;
383
- line-height: 1.6 !important;
384
- box-shadow: inset 0 2px 8px rgba(0,0,0,0.3) !important;
385
- }
386
-
387
- .loading {
388
- display: inline-block;
389
- width: 20px;
390
- height: 20px;
391
- border: 3px solid #f3f4f6;
392
- border-top-color: #667eea;
393
- border-radius: 50%;
394
- animation: spin 0.8s linear infinite;
395
- }
396
-
397
- @keyframes spin {
398
- to { transform: rotate(360deg); }
399
- }
400
-
401
- .response-time {
402
- font-weight: 700;
403
- padding: 4px 10px;
404
- border-radius: 8px;
405
- font-size: 13px;
406
- }
407
-
408
- .response-fast {
409
- background: #d1fae5;
410
- color: #065f46;
411
- }
412
-
413
- .response-medium {
414
- background: #fef3c7;
415
- color: #92400e;
416
- }
417
-
418
- .response-slow {
419
- background: #fee2e2;
420
- color: #991b1b;
421
- }
422
- </style>
423
  </head>
424
- <body>
425
- <div class="container">
426
- <h1>🚀 Crypto API Monitor</h1>
427
- <p class="subtitle">Real-time monitoring of cryptocurrency APIs with live data</p>
428
- <p class="last-update">⏱️ Last Update: <span id="lastUpdate">Loading...</span></p>
429
-
430
- <div style="margin: 20px 0;">
431
- <button class="refresh-btn" onclick="loadData()">🔄 Refresh Data</button>
432
- <button class="refresh-btn" onclick="window.location.href='/hf_console.html'">🤗 HF Console</button>
433
- <button class="refresh-btn" onclick="window.location.href='/admin.html'">⚙️ Admin Panel</button>
434
- <button class="refresh-btn" onclick="window.location.href='/index.html'">📊 Full Dashboard</button>
435
- </div>
436
-
437
- <div class="stats-grid">
438
- <div class="stat-card">
439
- <div class="stat-icon">📡</div>
440
- <div class="stat-label">Total APIs</div>
441
- <div class="stat-value" id="totalAPIs">0</div>
442
- </div>
443
- <div class="stat-card">
444
- <div class="stat-icon">✅</div>
445
- <div class="stat-label">Online</div>
446
- <div class="stat-value green" id="onlineAPIs">0</div>
447
- </div>
448
- <div class="stat-card">
449
- <div class="stat-icon">❌</div>
450
- <div class="stat-label">Offline</div>
451
- <div class="stat-value red" id="offlineAPIs">0</div>
452
- </div>
453
- <div class="stat-card">
454
- <div class="stat-icon">⚡</div>
455
- <div class="stat-label">Avg Response</div>
456
- <div class="stat-value orange" id="avgResponse" style="font-size: 32px;">0ms</div>
457
- </div>
458
- </div>
459
-
460
- <div class="section-header">
461
- <h2>📊 API Providers Status</h2>
462
- </div>
463
- <table class="providers-table">
464
- <thead>
465
- <tr>
466
- <th>Provider</th>
467
- <th>Category</th>
468
- <th>Status</th>
469
- <th>Response Time</th>
470
- <th>Last Check</th>
471
- </tr>
472
- </thead>
473
- <tbody id="providersTable">
474
- <tr><td colspan="5" style="text-align: center;">Loading...</td></tr>
475
- </tbody>
476
- </table>
477
-
478
- <div class="hf-section">
479
- <div class="section-header" style="border: none; margin: 0 0 20px 0;">
480
- <h2>🤗 HuggingFace Sentiment Analysis</h2>
481
- </div>
482
- <p style="color: #6c757d; margin-bottom: 10px;">Enter crypto-related text (one per line) to analyze sentiment using AI:</p>
483
- <textarea id="sentimentText" rows="5" placeholder="BTC strong breakout&#10;ETH looks weak&#10;Market is bullish">BTC strong breakout
484
- ETH looks weak
485
- Market is bullish today</textarea>
486
- <button class="refresh-btn" onclick="runSentiment()">🧠 Analyze Sentiment</button>
487
- <div class="sentiment-result" id="sentimentResult">—</div>
488
- <pre id="sentimentDetails" style="background: white; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 12px;"></pre>
489
  </div>
490
  </div>
 
 
 
 
 
 
 
491
 
492
- <script>
493
- async function loadData() {
494
- try {
495
- // Show loading state
496
- const tbody = document.getElementById('providersTable');
497
- if (tbody) {
498
- 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>';
499
- }
500
- if (document.getElementById('lastUpdate')) {
501
- document.getElementById('lastUpdate').textContent = 'در حال بارگذاری...';
502
- }
503
-
504
- // Load status
505
- const statusRes = await fetch('/api/status');
506
- if (!statusRes.ok) {
507
- throw new Error(`خطا در دریافت وضعیت: ${statusRes.status} ${statusRes.statusText}`);
508
- }
509
- const status = await statusRes.json();
510
-
511
- if (!status || typeof status.total_providers === 'undefined') {
512
- throw new Error('داده‌های وضعیت نامعتبر است');
513
- }
514
 
515
- if (document.getElementById('totalAPIs')) {
516
- document.getElementById('totalAPIs').textContent = status.total_providers || 0;
517
- }
518
- if (document.getElementById('onlineAPIs')) {
519
- document.getElementById('onlineAPIs').textContent = status.online || 0;
520
- }
521
- if (document.getElementById('offlineAPIs')) {
522
- document.getElementById('offlineAPIs').textContent = status.offline || 0;
523
- }
524
- if (document.getElementById('avgResponse')) {
525
- document.getElementById('avgResponse').textContent = (status.avg_response_time_ms || 0) + 'ms';
526
- }
527
- if (document.getElementById('lastUpdate')) {
528
- document.getElementById('lastUpdate').textContent = status.timestamp ? new Date(status.timestamp).toLocaleString('fa-IR') : 'نامشخص';
529
- }
530
-
531
- // Load providers
532
- const providersRes = await fetch('/api/providers');
533
- if (!providersRes.ok) {
534
- throw new Error(`خطا در دریافت لیست APIها: ${providersRes.status} ${providersRes.statusText}`);
535
- }
536
- const providers = await providersRes.json();
537
-
538
- if (!providers || !Array.isArray(providers)) {
539
- throw new Error('لیست APIها نامعتبر است');
540
- }
541
 
542
- if (tbody) {
543
- if (providers.length === 0) {
544
- tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px; color: #6c757d;">هیچ APIای یافت نشد</td></tr>';
545
- } else {
546
- tbody.innerHTML = providers.map(p => {
547
- let responseClass = 'response-fast';
548
- const responseTime = p.response_time_ms || p.avg_response_time_ms || 0;
549
- if (responseTime > 3000) responseClass = 'response-slow';
550
- else if (responseTime > 1000) responseClass = 'response-medium';
551
-
552
- return `
553
- <tr>
554
- <td><strong style="font-size: 15px;">${p.name || 'نامشخص'}</strong></td>
555
- <td><span style="background: #f8f9fa; padding: 4px 10px; border-radius: 8px; font-size: 12px; font-weight: 600;">${p.category || 'نامشخص'}</span></td>
556
- <td><span class="status-badge status-${p.status || 'unknown'}">${(p.status || 'unknown').toUpperCase()}</span></td>
557
- <td><span class="response-time ${responseClass}">${responseTime}ms</span></td>
558
- <td style="color: #6c757d; font-size: 13px;">${p.last_fetch ? new Date(p.last_fetch).toLocaleTimeString('fa-IR') : 'نامشخص'}</td>
559
- </tr>
560
- `}).join('');
561
- }
562
- }
563
-
564
- } catch (error) {
565
- console.error('Error loading data:', error);
566
- const tbody = document.getElementById('providersTable');
567
- if (tbody) {
568
- tbody.innerHTML = `<tr><td colspan="5" style="text-align: center; padding: 40px; color: #ef4444;">
569
- <div style="font-size: 24px; margin-bottom: 10px;">❌</div>
570
- <div style="font-weight: 600; margin-bottom: 5px;">خطا در بارگذاری داده‌ها</div>
571
- <div style="font-size: 14px; color: #6c757d; margin-bottom: 15px;">${error.message || 'خطای نامشخص'}</div>
572
- <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>
573
- </td></tr>`;
574
- }
575
- if (document.getElementById('lastUpdate')) {
576
- document.getElementById('lastUpdate').textContent = 'خطا در بارگذاری';
577
- }
578
- alert('❌ خطا در بارگذاری داده‌ها:\n' + (error.message || 'خطای نامشخص'));
579
- }
580
- }
581
 
582
- async function runSentiment() {
583
- const text = document.getElementById('sentimentText').value;
584
- const texts = text.split('\n').filter(t => t.trim());
585
-
586
- if (texts.length === 0) {
587
- alert('Please enter at least one line of text');
588
- return;
589
- }
590
-
591
- try {
592
- document.getElementById('sentimentResult').textContent = '⏳ Analyzing...';
593
- document.getElementById('sentimentDetails').textContent = '';
594
-
595
- const res = await fetch('/api/hf/run-sentiment', {
596
- method: 'POST',
597
- headers: { 'Content-Type': 'application/json' },
598
- body: JSON.stringify({ texts })
599
- });
600
-
601
- if (!res.ok) {
602
- throw new Error(`HTTP ${res.status}: ${await res.text()}`);
603
- }
604
-
605
- const data = await res.json();
606
-
607
- const vote = data.vote || 0;
608
- let emoji = '😐';
609
- let color = '#6c757d';
610
-
611
- if (vote > 0.2) {
612
- emoji = '📈';
613
- color = '#10b981';
614
- } else if (vote < -0.2) {
615
- emoji = '📉';
616
- color = '#ef4444';
617
- }
618
-
619
- document.getElementById('sentimentResult').innerHTML = `
620
- <span style="color: ${color};">${emoji} ${vote.toFixed(3)}</span>
621
- `;
622
- document.getElementById('sentimentDetails').textContent = JSON.stringify(data, null, 2);
623
-
624
- } catch (error) {
625
- console.error('Error running sentiment:', error);
626
- document.getElementById('sentimentResult').innerHTML = '<span style="color: #ef4444;">❌ Error</span>';
627
- document.getElementById('sentimentDetails').textContent = 'Error: ' + error.message;
628
- }
629
- }
630
 
631
- // Load data on page load
632
- loadData();
633
-
634
- // Auto-refresh every 30 seconds
635
- setInterval(loadData, 30000);
636
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
637
  </body>
638
- </html>
 
1
+ <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Crypto Intelligence Dashboard</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
10
+ <link rel="stylesheet" href="/static/css/unified-ui.css" />
11
+ <link rel="stylesheet" href="/static/css/components.css" />
12
+ <script defer src="/static/js/ui-feedback.js"></script>
13
+ <script defer src="/static/js/dashboard-app.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  </head>
15
+ <body class="page page-dashboard">
16
+ <header class="top-nav">
17
+ <div class="branding">
18
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/></svg>
19
+ <div>
20
+ <strong>Crypto Intelligence Hub</strong>
21
+ <small style="color:var(--ui-text-muted);letter-spacing:0.2em;">Real-time data + HF models</small>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
  </div>
24
+ <nav class="nav-links">
25
+ <a class="active" href="/dashboard">Dashboard</a>
26
+ <a href="/admin">Admin</a>
27
+ <a href="/hf_console">HF Console</a>
28
+ <a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
29
+ </nav>
30
+ </header>
31
 
32
+ <main class="page-content">
33
+ <section class="card" id="intro-card">
34
+ <div class="section-heading">
35
+ <h2>Unified Market Pulse</h2>
36
+ <span class="badge info" id="intro-source">Loading...</span>
37
+ </div>
38
+ <p style="color:var(--text-muted);max-width:780px;line-height:1.6;">
39
+ Live collectors + local fallback registry guarantee resilient insights. All numbers below already honor the FastAPI routes
40
+ (<code>/api/crypto/prices/top</code>, <code>/api/crypto/market-overview</code>, <code>/health</code>) so you can monitor status even when providers degrade.
41
+ </p>
42
+ </section>
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ <section class="card-grid" id="market-metrics">
45
+ <article class="card"><h3>Total Market Cap</h3><div class="metric-value" id="metric-market-cap">-</div><div class="metric-subtext" id="metric-cap-source"></div></article>
46
+ <article class="card"><h3>24h Volume</h3><div class="metric-value" id="metric-volume">-</div><div class="metric-subtext" id="metric-volume-source"></div></article>
47
+ <article class="card"><h3>BTC Dominance</h3><div class="metric-value" id="metric-btc-dom">-</div><div class="metric-subtext">Based on /api/crypto/market-overview</div></article>
48
+ <article class="card"><h3>System Health</h3><div class="metric-value" id="metric-health">-</div><div class="metric-subtext" id="metric-health-details"></div></article>
49
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ <section class="card table-card">
52
+ <div class="section-heading">
53
+ <h2>Top Assets</h2>
54
+ <span class="badge info" id="top-prices-source">Loading...</span>
55
+ </div>
56
+ <div class="table-wrapper">
57
+ <table>
58
+ <thead>
59
+ <tr><th>Symbol</th><th>Price</th><th>24h %</th><th>Volume</th></tr>
60
+ </thead>
61
+ <tbody id="top-prices-table">
62
+ <tr><td colspan="4">Loading...</td></tr>
63
+ </tbody>
64
+ </table>
65
+ </div>
66
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ <section class="split-grid">
69
+ <article class="card" id="overview-card">
70
+ <div class="section-heading">
71
+ <h2>Market Overview</h2>
72
+ <span class="badge info" id="market-overview-source">Loading...</span>
73
+ </div>
74
+ <ul class="list" id="market-overview-list"></ul>
75
+ </article>
76
+ <article class="card" id="system-card">
77
+ <div class="section-heading">
78
+ <h2>System & Rate Limits</h2>
79
+ <span class="badge info" id="system-status-source">/health</span>
80
+ </div>
81
+ <div id="system-health-status" class="metric-subtext"></div>
82
+ <ul class="list" id="system-status-list"></ul>
83
+ <div class="section-heading" style="margin-top:24px;">
84
+ <h2>Configuration</h2>
85
+ </div>
86
+ <ul class="list" id="system-config-list"></ul>
87
+ <div class="section-heading" style="margin-top:24px;">
88
+ <h2>Rate Limits</h2>
89
+ </div>
90
+ <ul class="list" id="rate-limits-list"></ul>
91
+ </article>
92
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ <section class="split-grid">
95
+ <article class="card" id="hf-widget">
96
+ <div class="section-heading">
97
+ <h2>HuggingFace Snapshot</h2>
98
+ <span class="badge info" id="hf-health-status">Loading...</span>
99
+ </div>
100
+ <div id="hf-widget-summary" class="metric-subtext"></div>
101
+ <ul class="list" id="hf-registry-list"></ul>
102
+ </article>
103
+ <article class="card">
104
+ <div class="section-heading">
105
+ <h2>Live Stream (/ws)</h2>
106
+ <span class="badge info" id="ws-status">Connecting...</span>
107
+ </div>
108
+ <div class="ws-stream" id="ws-stream"></div>
109
+ </article>
110
+ </section>
111
+ </main>
112
  </body>
113
+ </html>
hf_console.html CHANGED
@@ -1,343 +1,97 @@
1
- <!DOCTYPE html>
2
- <html lang="en" dir="rtl">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>HF Console - Crypto API Monitor</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
- padding: 20px;
13
- direction: rtl;
14
- }
15
- .container {
16
- max-width: 1400px;
17
- margin: 0 auto;
18
- background: white;
19
- border-radius: 16px;
20
- padding: 30px;
21
- box-shadow: 0 10px 40px rgba(0,0,0,0.2);
22
- }
23
- h1 {
24
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25
- -webkit-background-clip: text;
26
- -webkit-text-fill-color: transparent;
27
- margin-bottom: 10px;
28
- font-size: 32px;
29
- }
30
- .subtitle { color: #666; margin-bottom: 30px; }
31
- section {
32
- margin-bottom: 30px;
33
- padding: 20px;
34
- background: #f8f9fa;
35
- border-radius: 12px;
36
- border: 2px solid #e9ecef;
37
- }
38
- h3 {
39
- color: #333;
40
- margin-bottom: 15px;
41
- font-size: 20px;
42
- display: flex;
43
- align-items: center;
44
- gap: 10px;
45
- }
46
- .badge {
47
- display: inline-block;
48
- padding: 4px 12px;
49
- border-radius: 12px;
50
- font-size: 14px;
51
- font-weight: 600;
52
- }
53
- .badge-success { background: #d1fae5; color: #10b981; }
54
- .badge-warning { background: #fef3c7; color: #f59e0b; }
55
- .badge-info { background: #dbeafe; color: #3b82f6; }
56
- pre {
57
- background: #1e293b;
58
- color: #e2e8f0;
59
- padding: 15px;
60
- border-radius: 8px;
61
- overflow-x: auto;
62
- font-size: 13px;
63
- line-height: 1.6;
64
- max-height: 300px;
65
- overflow-y: auto;
66
- }
67
- button {
68
- padding: 10px 20px;
69
- border: none;
70
- border-radius: 8px;
71
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
72
- color: white;
73
- font-weight: 600;
74
- cursor: pointer;
75
- transition: all 0.3s;
76
- margin: 5px;
77
- }
78
- button:hover {
79
- transform: translateY(-2px);
80
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
81
- }
82
- button:active { transform: translateY(0); }
83
- input, textarea {
84
- width: 100%;
85
- padding: 10px;
86
- border: 2px solid #e9ecef;
87
- border-radius: 8px;
88
- font-family: inherit;
89
- margin: 10px 0;
90
- }
91
- input:focus, textarea:focus {
92
- outline: none;
93
- border-color: #667eea;
94
- }
95
- .list-box {
96
- max-height: 250px;
97
- overflow-y: auto;
98
- border: 1px solid #e9ecef;
99
- padding: 10px;
100
- background: white;
101
- border-radius: 8px;
102
- margin: 10px 0;
103
- }
104
- .list-box ul { list-style: none; }
105
- .list-box li {
106
- padding: 8px;
107
- border-bottom: 1px solid #f1f5f9;
108
- font-size: 14px;
109
- color: #475569;
110
- }
111
- .list-box li:last-child { border-bottom: none; }
112
- .vote-display {
113
- font-size: 24px;
114
- font-weight: 700;
115
- padding: 15px;
116
- background: white;
117
- border-radius: 8px;
118
- text-align: center;
119
- margin: 10px 0;
120
- }
121
- .vote-positive { color: #10b981; }
122
- .vote-negative { color: #ef4444; }
123
- .vote-neutral { color: #6b7280; }
124
- .loading {
125
- display: inline-block;
126
- width: 16px;
127
- height: 16px;
128
- border: 3px solid #f3f4f6;
129
- border-top-color: #667eea;
130
- border-radius: 50%;
131
- animation: spin 0.8s linear infinite;
132
- }
133
- @keyframes spin {
134
- to { transform: rotate(360deg); }
135
- }
136
- .grid {
137
- display: grid;
138
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
139
- gap: 20px;
140
- }
141
- </style>
142
- <!-- API Configuration -->
143
- <script src="config.js"></script>
144
  </head>
145
- <body>
146
- <div class="container">
147
- <h1>🤗 HuggingFace Console</h1>
148
- <p class="subtitle">Test HF connectivity, registry, search, and sentiment analysis</p>
149
- <div style="background: #f0f9ff; padding: 10px; border-radius: 8px; margin-bottom: 20px; font-size: 13px; color: #0369a1;">
150
- <strong>🌐 Environment:</strong> <span id="envInfo">Loading...</span> |
151
- <strong>📡 API:</strong> <span id="apiInfo">Loading...</span>
152
  </div>
 
 
 
 
 
 
 
 
153
 
154
- <section>
155
- <h3>
156
- <span>📊 Health Status</span>
157
- <span class="badge badge-info" id="healthBadge">Loading...</span>
158
- </h3>
159
- <button onclick="loadHealth()">🔄 Refresh Health</button>
160
- <button onclick="doRefresh()">🔃 Force Registry Refresh</button>
161
- <pre id="healthOutput">Loading...</pre>
162
- </section>
163
-
164
- <div class="grid">
165
- <section>
166
- <h3>
167
- <span>🤖 Models Registry</span>
168
- <span class="badge badge-success" id="modelsCount">0</span>
169
- </h3>
170
- <button onclick="loadModels()">Load Models</button>
171
- <div class="list-box" id="modelsList">
172
- <p style="color: #94a3b8;">Click "Load Models" to fetch...</p>
173
- </div>
174
- </section>
175
-
176
- <section>
177
- <h3>
178
- <span>📚 Datasets Registry</span>
179
- <span class="badge badge-success" id="datasetsCount">0</span>
180
- </h3>
181
- <button onclick="loadDatasets()">Load Datasets</button>
182
- <div class="list-box" id="datasetsList">
183
- <p style="color: #94a3b8;">Click "Load Datasets" to fetch...</p>
184
- </div>
185
- </section>
186
  </div>
 
 
 
187
 
188
- <section>
189
- <h3>🔍 Search Registry (Local Snapshot)</h3>
190
- <input type="text" id="searchQuery" placeholder="Search query (e.g., crypto, bitcoin, sentiment)" value="crypto">
191
- <button onclick="doSearch()">Search Models</button>
192
- <button onclick="doSearchDatasets()">Search Datasets</button>
193
- <div class="list-box" id="searchResults">
194
- <p style="color: #94a3b8;">Enter a query and click search...</p>
 
 
 
 
195
  </div>
196
- </section>
197
-
198
- <section>
199
- <h3>💭 Sentiment Analysis (Local Pipeline)</h3>
200
- <p style="color: #666; font-size: 14px; margin-bottom: 10px;">
201
- Enter text samples (one per line) to analyze crypto sentiment using local transformers
202
- </p>
203
- <textarea id="sentimentTexts" rows="5" placeholder="BTC looks strong&#10;ETH is weak today&#10;Market sentiment is bullish">BTC strong breakout
204
- ETH looks weak
205
- Crypto market is bullish today
206
- Bears are taking control
207
- Neutral market conditions</textarea>
208
- <button onclick="doSentiment()">🧠 Run Sentiment Analysis</button>
209
- <div class="vote-display" id="voteDisplay">
210
- <span style="color: #94a3b8;">—</span>
211
  </div>
212
- <pre id="sentimentOutput">Results will appear here...</pre>
213
- </section>
214
- </div>
215
-
216
- <script>
217
- // Use the CONFIG object from config.js
218
- const API_BASE = CONFIG.API_BASE;
219
- const fetchJSON = CONFIG.fetchJSON;
220
- const postJSON = CONFIG.postJSON;
221
-
222
- // Display environment info
223
- function updateEnvironmentInfo() {
224
- const envType = CONFIG.IS_HUGGINGFACE_SPACES ? '🤗 HuggingFace Spaces' :
225
- CONFIG.IS_LOCALHOST ? '💻 Localhost' : '🌐 Custom Deployment';
226
- document.getElementById('envInfo').textContent = envType;
227
- document.getElementById('apiInfo').textContent = CONFIG.API_BASE;
228
- }
229
-
230
- async function loadHealth() {
231
- try {
232
- const data = await fetchJSON(CONFIG.ENDPOINTS.HF_HEALTH);
233
- document.getElementById('healthOutput').textContent = JSON.stringify(data, null, 2);
234
- document.getElementById('healthBadge').textContent = data.ok ? '✓ Healthy' : '✗ Unhealthy';
235
- document.getElementById('healthBadge').className = data.ok ? 'badge badge-success' : 'badge badge-warning';
236
- } catch (err) {
237
- document.getElementById('healthOutput').textContent = `Error: ${err.message}`;
238
- document.getElementById('healthBadge').textContent = '✗ Error';
239
- document.getElementById('healthBadge').className = 'badge badge-warning';
240
- }
241
- }
242
-
243
- async function doRefresh() {
244
- try {
245
- document.getElementById('healthOutput').textContent = 'Refreshing registry...';
246
- const data = await postJSON(CONFIG.ENDPOINTS.HF_REFRESH, {});
247
- document.getElementById('healthOutput').textContent = JSON.stringify(data, null, 2);
248
- await loadHealth();
249
- } catch (err) {
250
- document.getElementById('healthOutput').textContent = `Error: ${err.message}`;
251
- }
252
- }
253
-
254
- async function loadModels() {
255
- try {
256
- const data = await fetchJSON(`${CONFIG.ENDPOINTS.HF_REGISTRY}?kind=models`);
257
- const items = data.items || [];
258
- document.getElementById('modelsCount').textContent = items.length;
259
- const html = items.length > 0
260
- ? '<ul>' + items.slice(0, 50).map(i => `<li>🤖 ${i.id} • ${i.pipeline_tag || 'N/A'} • <small>${i.source}</small></li>`).join('') + '</ul>'
261
- : '<p style="color: #94a3b8;">No models found</p>';
262
- document.getElementById('modelsList').innerHTML = html;
263
- } catch (err) {
264
- document.getElementById('modelsList').innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
265
- }
266
- }
267
-
268
- async function loadDatasets() {
269
- try {
270
- const data = await fetchJSON(`${CONFIG.ENDPOINTS.HF_REGISTRY}?kind=datasets`);
271
- const items = data.items || [];
272
- document.getElementById('datasetsCount').textContent = items.length;
273
- const html = items.length > 0
274
- ? '<ul>' + items.slice(0, 50).map(i => `<li>📚 ${i.id} • <small>${i.source}</small></li>`).join('') + '</ul>'
275
- : '<p style="color: #94a3b8;">No datasets found</p>';
276
- document.getElementById('datasetsList').innerHTML = html;
277
- } catch (err) {
278
- document.getElementById('datasetsList').innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
279
- }
280
- }
281
-
282
- async function doSearch() {
283
- const q = document.getElementById('searchQuery').value;
284
- try {
285
- const data = await fetchJSON(`${CONFIG.ENDPOINTS.HF_SEARCH}?q=${encodeURIComponent(q)}&kind=models`);
286
- const items = data.items || [];
287
- const html = items.length > 0
288
- ? `<p style="color: #10b981; font-weight: 600;">Found ${items.length} models</p><ul>` + items.map(i => `<li>🤖 ${i.id}</li>`).join('') + '</ul>'
289
- : '<p style="color: #94a3b8;">No results found</p>';
290
- document.getElementById('searchResults').innerHTML = html;
291
- } catch (err) {
292
- document.getElementById('searchResults').innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
293
- }
294
- }
295
-
296
- async function doSearchDatasets() {
297
- const q = document.getElementById('searchQuery').value;
298
- try {
299
- const data = await fetchJSON(`${CONFIG.ENDPOINTS.HF_SEARCH}?q=${encodeURIComponent(q)}&kind=datasets`);
300
- const items = data.items || [];
301
- const html = items.length > 0
302
- ? `<p style="color: #10b981; font-weight: 600;">Found ${items.length} datasets</p><ul>` + items.map(i => `<li>📚 ${i.id}</li>`).join('') + '</ul>'
303
- : '<p style="color: #94a3b8;">No results found</p>';
304
- document.getElementById('searchResults').innerHTML = html;
305
- } catch (err) {
306
- document.getElementById('searchResults').innerHTML = `<p style="color: #ef4444;">Error: ${err.message}</p>`;
307
- }
308
- }
309
-
310
- async function doSentiment() {
311
- const texts = document.getElementById('sentimentTexts').value.split('\n').filter(t => t.trim());
312
- if (texts.length === 0) {
313
- alert('Please enter at least one text sample');
314
- return;
315
- }
316
- try {
317
- document.getElementById('voteDisplay').innerHTML = '<span class="loading"></span>';
318
- document.getElementById('sentimentOutput').textContent = 'Running sentiment analysis...';
319
-
320
- const data = await postJSON(CONFIG.ENDPOINTS.HF_RUN_SENTIMENT, { texts });
321
-
322
- const vote = data.vote || 0;
323
- let voteClass = 'vote-neutral';
324
- let voteEmoji = '😐';
325
- if (vote > 0.2) { voteClass = 'vote-positive'; voteEmoji = '📈'; }
326
- else if (vote < -0.2) { voteClass = 'vote-negative'; voteEmoji = '📉'; }
327
-
328
- document.getElementById('voteDisplay').innerHTML = `<span class="${voteClass}">${voteEmoji} ${vote.toFixed(3)}</span>`;
329
- document.getElementById('sentimentOutput').textContent = JSON.stringify(data, null, 2);
330
- } catch (err) {
331
- document.getElementById('voteDisplay').innerHTML = '<span style="color: #ef4444;">Error</span>';
332
- document.getElementById('sentimentOutput').textContent = `Error: ${err.message}`;
333
- }
334
- }
335
 
336
- // Auto-load health and environment info on page load
337
- window.addEventListener('load', () => {
338
- updateEnvironmentInfo();
339
- loadHealth();
340
- });
341
- </script>
 
 
 
 
 
 
 
342
  </body>
343
- </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>HF Console · Crypto Intelligence</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
10
+ <link rel="stylesheet" href="/static/css/unified-ui.css" />
11
+ <link rel="stylesheet" href="/static/css/components.css" />
12
+ <script defer src="/static/js/ui-feedback.js"></script>
13
+ <script defer src="/static/js/hf-console.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  </head>
15
+ <body class="page page-hf-console">
16
+ <header class="top-nav">
17
+ <div class="branding">
18
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/></svg>
19
+ <div>
20
+ <strong>HF Models & Datasets</strong>
21
+ <small style="color:var(--ui-text-muted);letter-spacing:0.2em;">/api/hf/* endpoints</small>
22
  </div>
23
+ </div>
24
+ <nav class="nav-links">
25
+ <a href="/dashboard">Dashboard</a>
26
+ <a href="/admin">Admin</a>
27
+ <a class="active" href="/hf_console">HF Console</a>
28
+ <a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
29
+ </nav>
30
+ </header>
31
 
32
+ <main class="page-content">
33
+ <section class="card">
34
+ <div class="section-heading">
35
+ <h2>Registry & Status</h2>
36
+ <span class="badge info" id="hf-console-health">Loading...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </div>
38
+ <p class="metric-subtext" id="hf-console-summary"></p>
39
+ <ul class="list" id="hf-console-models"></ul>
40
+ </section>
41
 
42
+ <section class="split-grid">
43
+ <article class="card">
44
+ <div class="section-heading"><h2>Sentiment Playground</h2><span class="badge info">POST /api/hf/models/sentiment</span></div>
45
+ <div class="form-field">
46
+ <label for="sentiment-model">Sentiment model</label>
47
+ <select id="sentiment-model">
48
+ <option value="auto">auto (ensemble)</option>
49
+ <option value="cryptobert">cryptobert</option>
50
+ <option value="cryptobert_finbert">cryptobert_finbert</option>
51
+ <option value="tiny_crypto_lm">tiny_crypto_lm</option>
52
+ </select>
53
  </div>
54
+ <div class="form-field">
55
+ <label for="sentiment-texts">Texts (one per line)</label>
56
+ <textarea id="sentiment-texts" rows="5" placeholder="BTC is breaking out...\nETH looks weak..."></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
+ <button class="primary" id="run-sentiment">Run Sentiment</button>
59
+ <div id="sentiment-results" class="ws-stream" style="margin-top:16px;"></div>
60
+ </article>
61
+ <article class="card">
62
+ <div class="section-heading"><h2>Forecast Sandbox</h2><span class="badge info">POST /api/hf/models/forecast</span></div>
63
+ <div class="form-field">
64
+ <label for="forecast-model">Model</label>
65
+ <select id="forecast-model">
66
+ <option value="btc_lstm">btc_lstm</option>
67
+ <option value="btc_arima">btc_arima</option>
68
+ </select>
69
+ </div>
70
+ <div class="form-field">
71
+ <label for="forecast-series">Closing Prices (comma separated)</label>
72
+ <textarea id="forecast-series" rows="5" placeholder="67650, 67820, 68010, 68120"></textarea>
73
+ </div>
74
+ <div class="form-field">
75
+ <label for="forecast-steps">Future Steps</label>
76
+ <input type="number" id="forecast-steps" value="3" min="1" max="10" />
77
+ </div>
78
+ <button class="primary" id="run-forecast">Forecast</button>
79
+ <div id="forecast-results" class="ws-stream" style="margin-top:16px;"></div>
80
+ </article>
81
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ <section class="card">
84
+ <div class="section-heading">
85
+ <h2>HF Datasets</h2>
86
+ <span class="badge info">GET /api/hf/datasets/*</span>
87
+ </div>
88
+ <div class="button-row" style="margin-bottom:16px;">
89
+ <button class="secondary" data-dataset="market-ohlcv">Market OHLCV</button>
90
+ <button class="secondary" data-dataset="market-btc">BTC Technicals</button>
91
+ <button class="secondary" data-dataset="news-semantic">News Semantic</button>
92
+ </div>
93
+ <div id="dataset-output" class="ws-stream"></div>
94
+ </section>
95
+ </main>
96
  </body>
97
+ </html>
hf_unified_server.py CHANGED
@@ -36,6 +36,7 @@ from pathlib import Path
36
  import httpx
37
 
38
  from database import CryptoDatabase
 
39
  from ai_models import (
40
  analyze_chart_points,
41
  analyze_crypto_sentiment,
@@ -85,7 +86,6 @@ provider_collector = ProviderStatusCollector()
85
  WORKSPACE_ROOT = Path(__file__).parent
86
  PROVIDERS_CONFIG_PATH = settings.providers_config_path
87
  FALLBACK_RESOURCE_PATH = WORKSPACE_ROOT / "crypto_resources_unified_2025-11-11.json"
88
-
89
  LOG_DIR = WORKSPACE_ROOT / "logs"
90
  APL_REPORT_PATH = WORKSPACE_ROOT / "PROVIDER_AUTO_DISCOVERY_REPORT.json"
91
 
@@ -217,11 +217,7 @@ def _format_price_record(record: Dict[str, Any]) -> Dict[str, Any]:
217
 
218
 
219
  async def fetch_binance_ohlcv(symbol: str = "BTCUSDT", interval: str = "1h", limit: int = 100):
220
- """Fetch OHLCV data from Binance via the shared collector.
221
-
222
- If live data cannot be retrieved, this helper returns an empty list instead of
223
- using any local/mock fallback so that upstream endpoints can fail honestly.
224
- """
225
 
226
  try:
227
  candles = await market_collector.get_ohlcv(symbol, interval, limit)
@@ -235,7 +231,11 @@ async def fetch_binance_ohlcv(symbol: str = "BTCUSDT", interval: str = "1h", lim
235
  ]
236
  except CollectorError as exc:
237
  logger.error("Error fetching OHLCV: %s", exc)
238
- return []
 
 
 
 
239
 
240
 
241
  async def fetch_coingecko_prices(symbols: Optional[List[str]] = None, limit: int = 10):
@@ -260,8 +260,15 @@ async def fetch_coingecko_prices(symbols: Optional[List[str]] = None, limit: int
260
  return formatted, source
261
  except CollectorError as exc:
262
  logger.error("Error fetching aggregated prices: %s", exc)
263
- # No local/mock fallback – return an empty list so callers can signal real failure
264
- return [], source
 
 
 
 
 
 
 
265
 
266
 
267
  async def fetch_binance_ticker(symbol: str):
@@ -676,7 +683,7 @@ async def get_market():
676
  db.save_price(payload)
677
  except Exception as db_err:
678
  logger.warning(f"Failed to save market data to DB: {db_err}")
679
-
680
  return {
681
  "total_market_cap": overview.get("total_market_cap", 0),
682
  "btc_dominance": overview.get("btc_dominance", 0),
@@ -739,12 +746,14 @@ async def get_multiple_prices(symbols: str = Query("BTC,ETH,SOL", description="C
739
  except:
740
  continue
741
  if not prices_data:
742
- # Fallback to CoinGecko only – no local/mock data
743
  prices_data, source = await fetch_coingecko_prices(symbol_list)
744
 
745
- # If still empty, return an empty list with explicit source
746
  if not prices_data:
747
- source = "none"
 
 
 
748
 
749
  return {
750
  "symbols": symbol_list,
@@ -937,25 +946,14 @@ async def get_all_signals():
937
 
938
  @app.get("/api/sentiment")
939
  async def get_sentiment():
940
- """Get market sentiment data.
941
-
942
- This endpoint only returns a value when real news data is available.
943
- If providers fail, we return an explicit HTTP 503 instead of synthesizing
944
- sentiment from placeholder text.
945
- """
946
  try:
947
  news = await news_collector.get_latest_news(limit=5)
948
  except CollectorError as exc:
949
- logger.warning("Sentiment error due to news provider failure: %s", exc)
950
- raise HTTPException(status_code=503, detail="Sentiment data is currently unavailable")
951
-
952
- if not news:
953
- raise HTTPException(status_code=503, detail="Sentiment data is currently unavailable")
954
-
955
- text = " ".join(item.get("title", "") for item in news).strip()
956
- if not text:
957
- raise HTTPException(status_code=503, detail="Sentiment data is currently unavailable")
958
 
 
959
  analysis = analyze_market_text(text)
960
  score = analysis.get("signals", {}).get("crypto", {}).get("score", 0.0)
961
  normalized_value = int((score + 1) * 50)
@@ -1353,9 +1351,7 @@ async def hf_dataset_news(limit: int = Query(10, ge=3, le=25)):
1353
  source = "local-fallback"
1354
 
1355
  if not news:
1356
- # No mock dataset here – return an empty list when providers have no data
1357
- items = []
1358
- source = "none"
1359
  else:
1360
  items = []
1361
  for item in news:
@@ -1564,8 +1560,19 @@ async def get_market_stats():
1564
  }
1565
  except Exception as e:
1566
  logger.error(f"Error in /api/market/stats (duplicate): {e}")
1567
- # No fake zeroed stats – surface a clear error instead
1568
- raise HTTPException(status_code=503, detail="Market statistics are currently unavailable")
 
 
 
 
 
 
 
 
 
 
 
1569
 
1570
 
1571
  @app.get("/api/stats")
@@ -2448,7 +2455,6 @@ async def websocket_endpoint(websocket: WebSocket):
2448
  except:
2449
  pass
2450
 
2451
-
2452
  @app.get("/api/market/history")
2453
  async def get_market_history(symbol: str = "BTC", limit: int = 10):
2454
  """
@@ -2518,8 +2524,7 @@ async def get_recent_logs():
2518
  log_file = LOG_DIR / "server.log"
2519
  lines = tail_log_file(log_file, max_lines=200)
2520
  # Wrap plain text lines as structured entries
2521
- logs = [{"line": line.rstrip("
2522
- ")} for line in lines]
2523
  return {"logs": logs, "count": len(logs)}
2524
 
2525
 
@@ -2533,8 +2538,7 @@ async def get_error_logs():
2533
  log_file = LOG_DIR / "server.log"
2534
  lines = tail_log_file(log_file, max_lines=400)
2535
  error_lines = [line for line in lines if "ERROR" in line or "WARNING" in line]
2536
- logs = [{"line": line.rstrip("
2537
- ")} for line in error_lines[-200:]]
2538
  return {"errors": logs, "count": len(logs)}
2539
 
2540
 
 
36
  import httpx
37
 
38
  from database import CryptoDatabase
39
+
40
  from ai_models import (
41
  analyze_chart_points,
42
  analyze_crypto_sentiment,
 
86
  WORKSPACE_ROOT = Path(__file__).parent
87
  PROVIDERS_CONFIG_PATH = settings.providers_config_path
88
  FALLBACK_RESOURCE_PATH = WORKSPACE_ROOT / "crypto_resources_unified_2025-11-11.json"
 
89
  LOG_DIR = WORKSPACE_ROOT / "logs"
90
  APL_REPORT_PATH = WORKSPACE_ROOT / "PROVIDER_AUTO_DISCOVERY_REPORT.json"
91
 
 
217
 
218
 
219
  async def fetch_binance_ohlcv(symbol: str = "BTCUSDT", interval: str = "1h", limit: int = 100):
220
+ """Fetch OHLCV data from Binance via the shared collector."""
 
 
 
 
221
 
222
  try:
223
  candles = await market_collector.get_ohlcv(symbol, interval, limit)
 
231
  ]
232
  except CollectorError as exc:
233
  logger.error("Error fetching OHLCV: %s", exc)
234
+ fallback_symbol = _normalize_asset_symbol(symbol)
235
+ fallback = local_resource_service.get_ohlcv(fallback_symbol, interval, limit)
236
+ if fallback:
237
+ return fallback
238
+ return []
239
 
240
 
241
  async def fetch_coingecko_prices(symbols: Optional[List[str]] = None, limit: int = 10):
 
260
  return formatted, source
261
  except CollectorError as exc:
262
  logger.error("Error fetching aggregated prices: %s", exc)
263
+
264
+ fallback = (
265
+ local_resource_service.get_prices_for_symbols([sym for sym in symbols or []])
266
+ if symbols
267
+ else local_resource_service.get_top_prices(limit)
268
+ )
269
+ if fallback:
270
+ return fallback, "local-fallback"
271
+ return [], source
272
 
273
 
274
  async def fetch_binance_ticker(symbol: str):
 
683
  db.save_price(payload)
684
  except Exception as db_err:
685
  logger.warning(f"Failed to save market data to DB: {db_err}")
686
+
687
  return {
688
  "total_market_cap": overview.get("total_market_cap", 0),
689
  "btc_dominance": overview.get("btc_dominance", 0),
 
746
  except:
747
  continue
748
  if not prices_data:
749
+ # Fallback to CoinGecko
750
  prices_data, source = await fetch_coingecko_prices(symbol_list)
751
 
 
752
  if not prices_data:
753
+ fallback_prices = local_resource_service.get_prices_for_symbols(symbol_list)
754
+ if fallback_prices:
755
+ prices_data = fallback_prices
756
+ source = "local-fallback"
757
 
758
  return {
759
  "symbols": symbol_list,
 
946
 
947
  @app.get("/api/sentiment")
948
  async def get_sentiment():
949
+ """Get market sentiment data"""
 
 
 
 
 
950
  try:
951
  news = await news_collector.get_latest_news(limit=5)
952
  except CollectorError as exc:
953
+ logger.warning("Sentiment fallback due to news error: %s", exc)
954
+ news = []
 
 
 
 
 
 
 
955
 
956
+ text = " ".join(item.get("title", "") for item in news).strip() or "Crypto market update"
957
  analysis = analyze_market_text(text)
958
  score = analysis.get("signals", {}).get("crypto", {}).get("score", 0.0)
959
  normalized_value = int((score + 1) * 50)
 
1351
  source = "local-fallback"
1352
 
1353
  if not news:
1354
+ items = HF_SAMPLE_NEWS[:limit]
 
 
1355
  else:
1356
  items = []
1357
  for item in news:
 
1560
  }
1561
  except Exception as e:
1562
  logger.error(f"Error in /api/market/stats (duplicate): {e}")
1563
+ return {
1564
+ "success": True,
1565
+ "stats": {
1566
+ "total_market_cap": 0,
1567
+ "total_volume_24h": 0,
1568
+ "btc_dominance": 0,
1569
+ "eth_dominance": 0,
1570
+ "active_cryptocurrencies": 0,
1571
+ "markets": 0,
1572
+ "market_cap_change_24h": 0.0,
1573
+ "timestamp": datetime.now().isoformat()
1574
+ }
1575
+ }
1576
 
1577
 
1578
  @app.get("/api/stats")
 
2455
  except:
2456
  pass
2457
 
 
2458
  @app.get("/api/market/history")
2459
  async def get_market_history(symbol: str = "BTC", limit: int = 10):
2460
  """
 
2524
  log_file = LOG_DIR / "server.log"
2525
  lines = tail_log_file(log_file, max_lines=200)
2526
  # Wrap plain text lines as structured entries
2527
+ logs = [{"line": line.rstrip("\n")} for line in lines]
 
2528
  return {"logs": logs, "count": len(logs)}
2529
 
2530
 
 
2538
  log_file = LOG_DIR / "server.log"
2539
  lines = tail_log_file(log_file, max_lines=400)
2540
  error_lines = [line for line in lines if "ERROR" in line or "WARNING" in line]
2541
+ logs = [{"line": line.rstrip("\n")} for line in error_lines[-200:]]
 
2542
  return {"errors": logs, "count": len(logs)}
2543
 
2544
 
index (1).html ADDED
The diff for this file is too large to render. See raw diff
 
main.py CHANGED
@@ -1,43 +1,31 @@
1
  """
2
- Main entry point for HuggingFace Space / local Uvicorn.
3
- This file exposes `app` so that:
4
- * Hugging Face Spaces can auto-discover it (`main:app`)
5
- * You can run locally via: uvicorn main:app --host 0.0.0.0 --port 7860
6
  """
7
  from pathlib import Path
8
  import sys
9
 
10
- # Ensure project root is importable
11
- CURRENT_DIR = Path(__file__).resolve().parent
12
- if str(CURRENT_DIR) not in sys.path:
13
- sys.path.insert(0, str(CURRENT_DIR))
14
-
15
- _import_error = None
16
 
 
17
  try:
18
- # Canonical unified FastAPI backend with all endpoints used by index.html
19
- from hf_unified_server import app # type: ignore
20
- except Exception as e: # pragma: no cover - only used when something is really wrong
21
- _import_error = e
22
- # Minimal fallback app so deployment never hard-crashes,
23
- # but clearly reports what went wrong.
24
  from fastapi import FastAPI
25
-
26
- app = FastAPI(title="Crypto Intelligence Hub - Fallback")
27
-
28
  @app.get("/health")
29
  def health():
30
- return {
31
- "status": "error",
32
- "message": "Failed to import hf_unified_server.app",
33
- "detail": str(_import_error),
34
- }
35
-
36
  @app.get("/")
37
  def root():
38
- return {
39
- "message": "Crypto backend failed to load. "
40
- "Check logs and dependencies. See /health for details."
41
- }
42
 
 
43
  __all__ = ["app"]
 
1
  """
2
+ Main entry point for HuggingFace Space
3
+ Loads the unified API server with all endpoints
 
 
4
  """
5
  from pathlib import Path
6
  import sys
7
 
8
+ # Add current directory to path
9
+ current_dir = Path(__file__).resolve().parent
10
+ sys.path.insert(0, str(current_dir))
 
 
 
11
 
12
+ # Import the unified server app
13
  try:
14
+ from hf_unified_server import app
15
+ except ImportError as e:
16
+ print(f"Error importing hf_unified_server: {e}")
17
+ print("Falling back to basic app...")
18
+ # Fallback to basic FastAPI app
 
19
  from fastapi import FastAPI
20
+ app = FastAPI(title="Crypto API - Loading...")
21
+
 
22
  @app.get("/health")
23
  def health():
24
+ return {"status": "loading", "message": "Server is starting up..."}
25
+
 
 
 
 
26
  @app.get("/")
27
  def root():
28
+ return {"message": "Cryptocurrency Data API - Initializing..."}
 
 
 
29
 
30
+ # Export app for uvicorn
31
  __all__ = ["app"]
unified_dashboard.html CHANGED
@@ -8,54 +8,152 @@
8
  <link rel="stylesheet" href="static/css/design-system.css" />
9
  <link rel="stylesheet" href="static/css/dashboard.css" />
10
  <link rel="stylesheet" href="static/css/pro-dashboard.css" />
 
 
 
11
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js" defer></script>
 
 
 
12
  </head>
13
- <body data-theme="dark">
14
  <div class="app-shell">
15
- <aside class="sidebar">
16
- <div class="brand">
17
- <strong>Crypto Monitor HF</strong>
18
- <span class="env-pill">
19
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
20
  <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5" />
21
  <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5" />
22
  <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5" />
23
  </svg>
24
- HF Space
25
- </span>
 
 
 
 
 
 
 
 
26
  </div>
27
- <nav class="nav">
28
- <button class="nav-button active" data-nav="page-overview">Overview</button>
29
- <button class="nav-button" data-nav="page-market">Market</button>
30
- <button class="nav-button" data-nav="page-chart">Chart Lab</button>
31
- <button class="nav-button" data-nav="page-ai">Sentiment & AI</button>
32
- <button class="nav-button" data-nav="page-news">News</button>
33
- <button class="nav-button" data-nav="page-providers">Providers</button>
34
- <button class="nav-button" data-nav="page-api">API Explorer</button>
35
- <button class="nav-button" data-nav="page-debug">Diagnostics</button>
36
- <button class="nav-button" data-nav="page-datasets">Datasets & Models</button>
37
- <button class="nav-button" data-nav="page-settings">Settings</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  </nav>
39
  <div class="sidebar-footer">
40
- Unified crypto intelligence console<br />Realtime data • HF optimized
 
 
 
 
 
41
  </div>
42
  </aside>
43
  <main class="main-area">
44
- <header class="topbar">
45
- <div>
46
- <h1>Unified Intelligence Dashboard</h1>
47
- <p class="text-muted">Live market telemetry, AI signals, diagnostics, and provider health.</p>
48
- </div>
49
- <div class="status-group">
50
- <div class="status-pill" data-api-health data-state="warn">
51
- <span class="status-dot"></span>
52
- <span>checking</span>
53
  </div>
54
- <div class="status-pill" data-ws-status data-state="warn">
55
- <span class="status-dot"></span>
56
- <span>connecting</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
  </div>
 
 
 
59
  </header>
60
  <div class="page-container">
61
  <section id="page-overview" class="page active">
@@ -95,6 +193,30 @@
95
  <canvas id="sentiment-chart" height="220"></canvas>
96
  </div>
97
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  </section>
99
 
100
  <section id="page-market" class="page">
@@ -153,32 +275,53 @@
153
 
154
  <section id="page-chart" class="page">
155
  <div class="section-header">
156
- <h2 class="section-title">Chart Lab</h2>
157
  <div class="controls-bar">
158
- <select data-chart-symbol>
159
  <option value="BTC">BTC</option>
160
  <option value="ETH">ETH</option>
161
  <option value="SOL">SOL</option>
162
  <option value="BNB">BNB</option>
 
 
 
 
163
  </select>
164
- <div class="input-chip">
165
- <button class="ghost active" data-chart-timeframe="7d">7D</button>
166
- <button class="ghost" data-chart-timeframe="30d">30D</button>
167
- <button class="ghost" data-chart-timeframe="90d">90D</button>
 
168
  </div>
169
  </div>
170
  </div>
171
- <div class="glass-card">
172
- <canvas id="chart-lab-canvas" height="260"></canvas>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  </div>
174
- <div class="glass-card">
175
- <div class="controls-bar">
176
- <label><input type="checkbox" data-indicator value="MA20" checked /> MA 20</label>
177
- <label><input type="checkbox" data-indicator value="MA50" /> MA 50</label>
178
- <label><input type="checkbox" data-indicator value="RSI" /> RSI</label>
179
- <label><input type="checkbox" data-indicator value="Volume" /> Volume</label>
180
  </div>
181
- <button class="primary" data-run-analysis>Analyze Chart with AI</button>
182
  <div data-ai-insights class="ai-insights"></div>
183
  </div>
184
  </section>
 
8
  <link rel="stylesheet" href="static/css/design-system.css" />
9
  <link rel="stylesheet" href="static/css/dashboard.css" />
10
  <link rel="stylesheet" href="static/css/pro-dashboard.css" />
11
+ <link rel="stylesheet" href="static/css/modern-dashboard.css" />
12
+ <link rel="stylesheet" href="static/css/glassmorphism.css" />
13
+ <link rel="stylesheet" href="static/css/light-minimal-theme.css" />
14
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js" defer></script>
15
+ <script src="static/js/animations.js" defer></script>
16
+ <script src="static/js/menu-system.js" defer></script>
17
+ <script src="static/js/huggingface-integration.js" defer></script>
18
  </head>
19
+ <body data-theme="light">
20
  <div class="app-shell">
21
+ <aside class="sidebar sidebar-modern">
22
+ <div class="brand brand-modern">
23
+ <div class="brand-icon">
24
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 
25
  <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5" />
26
  <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5" />
27
  <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5" />
28
  </svg>
29
+ </div>
30
+ <div class="brand-text">
31
+ <strong>Crypto Monitor HF</strong>
32
+ <span class="env-pill">
33
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
34
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5" />
35
+ </svg>
36
+ HF Space
37
+ </span>
38
+ </div>
39
  </div>
40
+ <nav class="nav nav-modern">
41
+ <button class="nav-button nav-button-modern active" data-nav="page-overview">
42
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
43
+ <path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
44
+ <path d="M9 22V12h6v10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
45
+ </svg>
46
+ <span>Overview</span>
47
+ </button>
48
+ <button class="nav-button nav-button-modern" data-nav="page-market">
49
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
50
+ <path d="M3 3v18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
51
+ <path d="M7 10l4-4 4 4 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
52
+ </svg>
53
+ <span>Market</span>
54
+ </button>
55
+ <button class="nav-button nav-button-modern" data-nav="page-chart">
56
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
57
+ <path d="M3 3v18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
58
+ <path d="M7 16l4-4 4 4 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
59
+ </svg>
60
+ <span>Chart Lab</span>
61
+ </button>
62
+ <button class="nav-button nav-button-modern" data-nav="page-ai">
63
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
64
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
65
+ </svg>
66
+ <span>Sentiment & AI</span>
67
+ </button>
68
+ <button class="nav-button nav-button-modern" data-nav="page-news">
69
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
70
+ <path d="M4 19.5A2.5 2.5 0 016.5 17H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
71
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
72
+ </svg>
73
+ <span>News</span>
74
+ </button>
75
+ <button class="nav-button nav-button-modern" data-nav="page-providers">
76
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
77
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
78
+ <path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
79
+ </svg>
80
+ <span>Providers</span>
81
+ </button>
82
+ <button class="nav-button nav-button-modern" data-nav="page-api">
83
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
84
+ <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
85
+ </svg>
86
+ <span>API Explorer</span>
87
+ </button>
88
+ <button class="nav-button nav-button-modern" data-nav="page-debug">
89
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
90
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
91
+ <path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
92
+ </svg>
93
+ <span>Diagnostics</span>
94
+ </button>
95
+ <button class="nav-button nav-button-modern" data-nav="page-datasets">
96
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
97
+ <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
98
+ </svg>
99
+ <span>Datasets & Models</span>
100
+ </button>
101
+ <button class="nav-button nav-button-modern" data-nav="page-settings">
102
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
103
+ <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2"/>
104
+ <path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
105
+ </svg>
106
+ <span>Settings</span>
107
+ </button>
108
  </nav>
109
  <div class="sidebar-footer">
110
+ <div class="footer-badge">
111
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
112
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
113
+ </svg>
114
+ Unified Intelligence Console
115
+ </div>
116
  </div>
117
  </aside>
118
  <main class="main-area">
119
+ <header class="modern-header">
120
+ <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; flex-wrap: wrap; gap: 16px;">
121
+ <div>
122
+ <h1 style="margin: 0; font-size: 1.75rem; font-weight: 800; background: linear-gradient(135deg, #00D4FF, #8B5CF6, #EC4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">Unified Intelligence Dashboard</h1>
123
+ <p class="text-muted" style="margin: 4px 0 0 0; font-size: 0.875rem;">Live market telemetry, AI signals, diagnostics, and provider health.</p>
 
 
 
 
124
  </div>
125
+ <div class="status-group" style="display: flex; gap: 12px; align-items: center; position: relative;">
126
+ <div class="status-pill" data-api-health data-state="warn" style="padding: 8px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1);">
127
+ <span class="status-dot"></span>
128
+ <span>checking</span>
129
+ </div>
130
+ <div class="status-pill" data-ws-status data-state="warn" style="padding: 8px 16px; border-radius: 12px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1);">
131
+ <span class="status-dot"></span>
132
+ <span>connecting</span>
133
+ </div>
134
+ <button class="button-3d" data-menu-trigger="theme-menu" style="padding: 8px 16px; position: relative;">
135
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
136
+ <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2"/>
137
+ <path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
138
+ </svg>
139
+ </button>
140
+ <div class="menu-dropdown" data-menu="theme-menu" style="display: none; top: 100%; right: 0; margin-top: 8px;">
141
+ <div class="menu-item" data-action="theme-light">
142
+ <span>☀️ Light Theme</span>
143
+ </div>
144
+ <div class="menu-item" data-action="theme-dark">
145
+ <span>🌙 Dark Theme</span>
146
+ </div>
147
+ <div class="menu-separator"></div>
148
+ <div class="menu-item" data-action="settings">
149
+ <span>⚙️ Settings</span>
150
+ </div>
151
+ </div>
152
  </div>
153
  </div>
154
+ <div class="header-crypto-list" data-header-crypto-list style="margin-top: 16px; width: 100%;">
155
+ <!-- Crypto list will be populated by JavaScript -->
156
+ </div>
157
  </header>
158
  <div class="page-container">
159
  <section id="page-overview" class="page active">
 
193
  <canvas id="sentiment-chart" height="220"></canvas>
194
  </div>
195
  </div>
196
+ <div class="glass-card" style="margin-top: 24px;">
197
+ <div class="section-header">
198
+ <h3>Backend Information</h3>
199
+ <span class="text-muted">System Status</span>
200
+ </div>
201
+ <div data-backend-info class="backend-info-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 16px;">
202
+ <div class="backend-info-item">
203
+ <span class="info-label">API Status</span>
204
+ <span class="info-value" data-api-status>Checking...</span>
205
+ </div>
206
+ <div class="backend-info-item">
207
+ <span class="info-label">WebSocket</span>
208
+ <span class="info-value" data-ws-status>Connecting...</span>
209
+ </div>
210
+ <div class="backend-info-item">
211
+ <span class="info-label">Providers</span>
212
+ <span class="info-value" data-providers-count>—</span>
213
+ </div>
214
+ <div class="backend-info-item">
215
+ <span class="info-label">Last Update</span>
216
+ <span class="info-value" data-last-update>—</span>
217
+ </div>
218
+ </div>
219
+ </div>
220
  </section>
221
 
222
  <section id="page-market" class="page">
 
275
 
276
  <section id="page-chart" class="page">
277
  <div class="section-header">
278
+ <h2 class="section-title">Chart Lab - TradingView Style</h2>
279
  <div class="controls-bar">
280
+ <select data-chart-symbol style="padding: 8px 12px; border-radius: 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); color: var(--text-primary);">
281
  <option value="BTC">BTC</option>
282
  <option value="ETH">ETH</option>
283
  <option value="SOL">SOL</option>
284
  <option value="BNB">BNB</option>
285
+ <option value="ADA">ADA</option>
286
+ <option value="DOT">DOT</option>
287
+ <option value="MATIC">MATIC</option>
288
+ <option value="AVAX">AVAX</option>
289
  </select>
290
+ <div class="chart-toolbar">
291
+ <button class="chart-timeframe-btn active" data-chart-timeframe="1d">1D</button>
292
+ <button class="chart-timeframe-btn" data-chart-timeframe="7d">7D</button>
293
+ <button class="chart-timeframe-btn" data-chart-timeframe="30d">30D</button>
294
+ <button class="chart-timeframe-btn" data-chart-timeframe="90d">90D</button>
295
  </div>
296
  </div>
297
  </div>
298
+ <div class="tradingview-chart-container glass-vibrant">
299
+ <div class="chart-toolbar">
300
+ <div class="chart-indicators">
301
+ <label class="chart-indicator-toggle">
302
+ <input type="checkbox" data-indicator="MA20" checked />
303
+ <span>MA 20</span>
304
+ </label>
305
+ <label class="chart-indicator-toggle">
306
+ <input type="checkbox" data-indicator="MA50" />
307
+ <span>MA 50</span>
308
+ </label>
309
+ <label class="chart-indicator-toggle">
310
+ <input type="checkbox" data-indicator="RSI" />
311
+ <span>RSI</span>
312
+ </label>
313
+ <label class="chart-indicator-toggle">
314
+ <input type="checkbox" data-indicator="Volume" checked />
315
+ <span>Volume</span>
316
+ </label>
317
+ </div>
318
+ </div>
319
+ <canvas id="chart-lab-canvas" height="400"></canvas>
320
  </div>
321
+ <div class="glass-card glass-vibrant" style="margin-top: 24px;">
322
+ <div class="controls-bar" style="margin-bottom: 16px;">
323
+ <button class="primary" data-run-analysis style="background: linear-gradient(135deg, #00D4FF, #8B5CF6);">Analyze Chart with AI</button>
 
 
 
324
  </div>
 
325
  <div data-ai-insights class="ai-insights"></div>
326
  </div>
327
  </section>