| |
| |
| |
| |
|
|
| import { modelsClient } from '../core/models-client.js'; |
|
|
| |
| |
| |
| function getModelsPagePath() { |
| const basePath = window.location.pathname.includes('/static/') |
| ? window.location.pathname.split('/static/')[0] + '/static' |
| : '/static'; |
| return `${basePath}/pages/models/index.html`; |
| } |
|
|
| |
| |
| |
| export async function renderModelStatusWidget(containerId) { |
| const container = document.getElementById(containerId); |
| if (!container) { |
| console.error(`Container ${containerId} not found`); |
| return; |
| } |
|
|
| |
| container.innerHTML = ` |
| <div class="model-status-widget loading"> |
| <div class="spinner"></div> |
| <p>Loading AI models status...</p> |
| </div> |
| `; |
|
|
| try { |
| |
| const summary = await modelsClient.getModelsSummary(); |
| |
| if (!summary.ok) { |
| container.innerHTML = ` |
| <div class="model-status-widget error"> |
| <h3>⚠️ Models Status</h3> |
| <p class="error-message">${summary.error || 'Failed to load models'}</p> |
| <p class="fallback-note">Using fallback sentiment analysis</p> |
| </div> |
| `; |
| return; |
| } |
|
|
| const stats = summary.summary; |
| |
| |
| container.innerHTML = ` |
| <div class="model-status-widget"> |
| <div class="widget-header"> |
| <h3>🤖 AI Models</h3> |
| <span class="hf-mode-badge">${stats.hf_mode}</span> |
| </div> |
| |
| <div class="stats-grid"> |
| <div class="stat-card"> |
| <div class="stat-value">${stats.total_models}</div> |
| <div class="stat-label">Total</div> |
| </div> |
| <div class="stat-card loaded"> |
| <div class="stat-value">${stats.loaded_models}</div> |
| <div class="stat-label">Loaded</div> |
| </div> |
| <div class="stat-card ${stats.failed_models > 0 ? 'warning' : ''}"> |
| <div class="stat-value">${stats.failed_models}</div> |
| <div class="stat-label">Failed</div> |
| </div> |
| </div> |
| |
| <div class="categories-section"> |
| <h4>Models by Category</h4> |
| <div id="${containerId}-categories" class="categories-list"></div> |
| </div> |
| |
| <div class="widget-footer"> |
| <button onclick="window.location.href=getModelsPagePath()" class="btn-view-all"> |
| View All Models → |
| </button> |
| </div> |
| </div> |
| `; |
|
|
| |
| renderCategories(`${containerId}-categories`, summary.categories); |
|
|
| } catch (error) { |
| console.error('Error rendering model status widget:', error); |
| container.innerHTML = ` |
| <div class="model-status-widget error"> |
| <h3>⚠️ Models Status</h3> |
| <p class="error-message">Failed to load: ${error.message}</p> |
| </div> |
| `; |
| } |
| } |
|
|
| |
| |
| |
| function renderCategories(containerId, categories) { |
| const container = document.getElementById(containerId); |
| if (!container || !categories) return; |
|
|
| let html = ''; |
| |
| for (const [category, models] of Object.entries(categories)) { |
| const loaded = models.filter(m => m.loaded).length; |
| const healthy = models.filter(m => m.status === 'healthy').length; |
| |
| html += ` |
| <div class="category-item"> |
| <div class="category-header"> |
| <span class="category-name">${formatCategoryName(category)}</span> |
| <span class="category-count">${loaded}/${models.length}</span> |
| </div> |
| <div class="category-progress"> |
| <div class="progress-fill" style="width: ${(loaded / models.length * 100)}%"></div> |
| </div> |
| </div> |
| `; |
| } |
|
|
| container.innerHTML = html; |
| } |
|
|
| |
| |
| |
| function formatCategoryName(category) { |
| const names = { |
| 'sentiment_crypto': 'Crypto Sentiment', |
| 'sentiment_social': 'Social Sentiment', |
| 'sentiment_financial': 'Financial Sentiment', |
| 'sentiment_news': 'News Sentiment', |
| 'analysis_generation': 'AI Analysis', |
| 'trading_signal': 'Trading Signals', |
| 'summarization': 'Summarization', |
| 'legacy': 'Legacy' |
| }; |
| |
| return names[category] || category; |
| } |
|
|
| |
| |
| |
| export const modelStatusWidgetCSS = ` |
| .model-status-widget { |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| border-radius: 16px; |
| padding: 1.5rem; |
| } |
| |
| .model-status-widget.loading { |
| text-align: center; |
| padding: 2rem; |
| } |
| |
| .model-status-widget.error { |
| border-color: rgba(239, 68, 68, 0.3); |
| background: rgba(239, 68, 68, 0.1); |
| } |
| |
| .widget-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 1.5rem; |
| } |
| |
| .widget-header h3 { |
| margin: 0; |
| font-size: 1.25rem; |
| font-weight: 600; |
| } |
| |
| .hf-mode-badge { |
| padding: 0.25rem 0.75rem; |
| background: rgba(45, 212, 191, 0.2); |
| border: 1px solid rgba(45, 212, 191, 0.3); |
| border-radius: 999px; |
| font-size: 0.75rem; |
| font-weight: 600; |
| text-transform: uppercase; |
| } |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 1rem; |
| margin-bottom: 1.5rem; |
| } |
| |
| .stat-card { |
| text-align: center; |
| padding: 1rem; |
| background: rgba(0, 0, 0, 0.3); |
| border-radius: 12px; |
| } |
| |
| .stat-card.loaded { |
| background: rgba(34, 197, 94, 0.1); |
| border: 1px solid rgba(34, 197, 94, 0.2); |
| } |
| |
| .stat-card.warning { |
| background: rgba(239, 68, 68, 0.1); |
| border: 1px solid rgba(239, 68, 68, 0.2); |
| } |
| |
| .stat-value { |
| font-size: 2rem; |
| font-weight: 700; |
| color: #2dd4bf; |
| } |
| |
| .stat-label { |
| font-size: 0.875rem; |
| color: rgba(255, 255, 255, 0.6); |
| margin-top: 0.25rem; |
| } |
| |
| .categories-section h4 { |
| font-size: 1rem; |
| font-weight: 600; |
| margin-bottom: 1rem; |
| color: rgba(255, 255, 255, 0.8); |
| } |
| |
| .categories-list { |
| display: flex; |
| flex-direction: column; |
| gap: 0.75rem; |
| } |
| |
| .category-item { |
| padding: 0.75rem; |
| background: rgba(0, 0, 0, 0.2); |
| border-radius: 8px; |
| } |
| |
| .category-header { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 0.5rem; |
| } |
| |
| .category-name { |
| font-weight: 500; |
| } |
| |
| .category-count { |
| color: rgba(255, 255, 255, 0.6); |
| font-size: 0.875rem; |
| } |
| |
| .category-progress { |
| height: 4px; |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 999px; |
| overflow: hidden; |
| } |
| |
| .progress-fill { |
| height: 100%; |
| background: linear-gradient(90deg, #2dd4bf, #818cf8); |
| transition: width 0.3s; |
| } |
| |
| .widget-footer { |
| margin-top: 1.5rem; |
| text-align: center; |
| } |
| |
| .btn-view-all { |
| padding: 0.75rem 1.5rem; |
| background: linear-gradient(135deg, #2dd4bf, #818cf8); |
| border: none; |
| border-radius: 8px; |
| color: white; |
| font-weight: 600; |
| cursor: pointer; |
| transition: transform 0.2s; |
| } |
| |
| .btn-view-all:hover { |
| transform: translateY(-2px); |
| } |
| |
| .error-message { |
| color: #fca5a5; |
| margin: 0.5rem 0; |
| } |
| |
| .fallback-note { |
| color: rgba(255, 255, 255, 0.6); |
| font-size: 0.875rem; |
| margin-top: 0.5rem; |
| } |
| `; |
|
|
|
|