Upload 47 files
Browse files- api_server_extended.py +16 -0
- static/css/main.css +21 -8
- static/js/app.js +72 -23
api_server_extended.py
CHANGED
|
@@ -902,14 +902,30 @@ async def get_status():
|
|
| 902 |
except Exception:
|
| 903 |
pass
|
| 904 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 905 |
return {
|
| 906 |
"status": "ok",
|
|
|
|
| 907 |
"timestamp": datetime.now().isoformat(),
|
|
|
|
| 908 |
"providers": {
|
| 909 |
"total": len(providers),
|
| 910 |
"free": free_count,
|
| 911 |
"paid": paid_count
|
| 912 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 913 |
"resources": resources_data,
|
| 914 |
"models": {
|
| 915 |
"total": model_count
|
|
|
|
| 902 |
except Exception:
|
| 903 |
pass
|
| 904 |
|
| 905 |
+
# Get system health metrics
|
| 906 |
+
online_count = 0
|
| 907 |
+
degraded_count = 0
|
| 908 |
+
offline_count = 0
|
| 909 |
+
response_times = []
|
| 910 |
+
|
| 911 |
+
# Try to get health status from providers if available
|
| 912 |
+
# This is a simplified version - in production you'd check actual provider health
|
| 913 |
+
system_health = "ok" if len(providers) > 0 else "unknown"
|
| 914 |
+
|
| 915 |
return {
|
| 916 |
"status": "ok",
|
| 917 |
+
"system_health": system_health,
|
| 918 |
"timestamp": datetime.now().isoformat(),
|
| 919 |
+
"last_update": datetime.now().isoformat(),
|
| 920 |
"providers": {
|
| 921 |
"total": len(providers),
|
| 922 |
"free": free_count,
|
| 923 |
"paid": paid_count
|
| 924 |
},
|
| 925 |
+
"online": online_count,
|
| 926 |
+
"degraded": degraded_count,
|
| 927 |
+
"offline": offline_count,
|
| 928 |
+
"avg_response_time_ms": round(sum(response_times) / len(response_times), 2) if response_times else 0,
|
| 929 |
"resources": resources_data,
|
| 930 |
"models": {
|
| 931 |
"total": model_count
|
static/css/main.css
CHANGED
|
@@ -470,23 +470,36 @@ body::before {
|
|
| 470 |
/* Stats Grid */
|
| 471 |
.stats-grid {
|
| 472 |
display: grid;
|
| 473 |
-
grid-template-columns: repeat(
|
| 474 |
gap: 20px;
|
| 475 |
margin-bottom: 30px;
|
| 476 |
}
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
.stat-card {
|
| 479 |
background: linear-gradient(135deg, rgba(17, 24, 39, 0.8), rgba(31, 41, 55, 0.6));
|
| 480 |
border: 1px solid var(--border);
|
| 481 |
border-radius: 16px;
|
| 482 |
-
padding:
|
| 483 |
display: flex;
|
| 484 |
align-items: center;
|
| 485 |
-
gap:
|
| 486 |
transition: all var(--transition-normal);
|
| 487 |
backdrop-filter: blur(10px);
|
| 488 |
position: relative;
|
| 489 |
overflow: hidden;
|
|
|
|
| 490 |
}
|
| 491 |
|
| 492 |
.stat-card::before {
|
|
@@ -527,13 +540,13 @@ body::before {
|
|
| 527 |
}
|
| 528 |
|
| 529 |
.stat-icon {
|
| 530 |
-
width:
|
| 531 |
-
height:
|
| 532 |
-
border-radius:
|
| 533 |
display: flex;
|
| 534 |
align-items: center;
|
| 535 |
justify-content: center;
|
| 536 |
-
font-size:
|
| 537 |
flex-shrink: 0;
|
| 538 |
}
|
| 539 |
|
|
@@ -566,7 +579,7 @@ body::before {
|
|
| 566 |
}
|
| 567 |
|
| 568 |
.stat-value {
|
| 569 |
-
font-size:
|
| 570 |
font-weight: 800;
|
| 571 |
color: var(--primary);
|
| 572 |
margin-bottom: 5px;
|
|
|
|
| 470 |
/* Stats Grid */
|
| 471 |
.stats-grid {
|
| 472 |
display: grid;
|
| 473 |
+
grid-template-columns: repeat(4, 1fr);
|
| 474 |
gap: 20px;
|
| 475 |
margin-bottom: 30px;
|
| 476 |
}
|
| 477 |
|
| 478 |
+
@media (max-width: 1400px) {
|
| 479 |
+
.stats-grid {
|
| 480 |
+
grid-template-columns: repeat(2, 1fr);
|
| 481 |
+
}
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
@media (max-width: 768px) {
|
| 485 |
+
.stats-grid {
|
| 486 |
+
grid-template-columns: 1fr;
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
.stat-card {
|
| 491 |
background: linear-gradient(135deg, rgba(17, 24, 39, 0.8), rgba(31, 41, 55, 0.6));
|
| 492 |
border: 1px solid var(--border);
|
| 493 |
border-radius: 16px;
|
| 494 |
+
padding: 20px;
|
| 495 |
display: flex;
|
| 496 |
align-items: center;
|
| 497 |
+
gap: 15px;
|
| 498 |
transition: all var(--transition-normal);
|
| 499 |
backdrop-filter: blur(10px);
|
| 500 |
position: relative;
|
| 501 |
overflow: hidden;
|
| 502 |
+
min-height: 120px;
|
| 503 |
}
|
| 504 |
|
| 505 |
.stat-card::before {
|
|
|
|
| 540 |
}
|
| 541 |
|
| 542 |
.stat-icon {
|
| 543 |
+
width: 60px;
|
| 544 |
+
height: 60px;
|
| 545 |
+
border-radius: 14px;
|
| 546 |
display: flex;
|
| 547 |
align-items: center;
|
| 548 |
justify-content: center;
|
| 549 |
+
font-size: 28px;
|
| 550 |
flex-shrink: 0;
|
| 551 |
}
|
| 552 |
|
|
|
|
| 579 |
}
|
| 580 |
|
| 581 |
.stat-value {
|
| 582 |
+
font-size: 32px;
|
| 583 |
font-weight: 800;
|
| 584 |
color: var(--primary);
|
| 585 |
margin-bottom: 5px;
|
static/js/app.js
CHANGED
|
@@ -463,22 +463,51 @@ async function loadDashboard() {
|
|
| 463 |
providersEl.textContent = providers;
|
| 464 |
}
|
| 465 |
|
| 466 |
-
// Display system status
|
| 467 |
const systemStatusDiv = document.getElementById('system-status');
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
} else {
|
| 483 |
throw new Error('Status endpoint not available');
|
| 484 |
}
|
|
@@ -904,14 +933,16 @@ async function loadModels() {
|
|
| 904 |
</div>
|
| 905 |
`;
|
| 906 |
|
| 907 |
-
// Update stat-models in dashboard and sidebar
|
| 908 |
const modelsStatEl = document.getElementById('stat-models');
|
| 909 |
const sidebarModelsEl = document.getElementById('sidebar-models');
|
|
|
|
|
|
|
| 910 |
if (modelsStatEl) {
|
| 911 |
-
modelsStatEl.textContent =
|
| 912 |
}
|
| 913 |
if (sidebarModelsEl) {
|
| 914 |
-
sidebarModelsEl.textContent =
|
| 915 |
}
|
| 916 |
} else {
|
| 917 |
throw new Error('Models status endpoint not available');
|
|
@@ -988,6 +1019,11 @@ async function loadModels() {
|
|
| 988 |
async function initializeModels() {
|
| 989 |
ToastManager.info('Initializing models... This may take a moment.');
|
| 990 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 991 |
try {
|
| 992 |
const response = await fetch('/api/models/initialize', { method: 'POST' });
|
| 993 |
if (!response.ok) {
|
|
@@ -996,15 +1032,28 @@ async function initializeModels() {
|
|
| 996 |
|
| 997 |
const data = await response.json();
|
| 998 |
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
} else {
|
| 1003 |
-
ToastManager.warning('
|
| 1004 |
}
|
|
|
|
|
|
|
|
|
|
| 1005 |
} catch (error) {
|
| 1006 |
console.error('Error initializing models:', error);
|
| 1007 |
-
ToastManager.error('Failed to initialize models');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1008 |
}
|
| 1009 |
}
|
| 1010 |
|
|
|
|
| 463 |
providersEl.textContent = providers;
|
| 464 |
}
|
| 465 |
|
| 466 |
+
// Display system status - handle different response formats
|
| 467 |
const systemStatusDiv = document.getElementById('system-status');
|
| 468 |
+
if (systemStatusDiv) {
|
| 469 |
+
// Try to get health status from different possible fields
|
| 470 |
+
const healthStatus = statusData.system_health || statusData.status || 'ok';
|
| 471 |
+
const healthClass = healthStatus === 'healthy' || healthStatus === 'ok' ? 'alert-success' :
|
| 472 |
+
healthStatus === 'degraded' ? 'alert-warning' : 'alert-error';
|
| 473 |
+
|
| 474 |
+
// Get provider counts
|
| 475 |
+
const providers = statusData.providers || {};
|
| 476 |
+
const totalProviders = providers.total || statusData.total_apis || 0;
|
| 477 |
+
const onlineProviders = statusData.online || 0;
|
| 478 |
+
const degradedProviders = statusData.degraded || 0;
|
| 479 |
+
const offlineProviders = statusData.offline || 0;
|
| 480 |
+
const avgResponseTime = statusData.avg_response_time_ms || 0;
|
| 481 |
+
const lastUpdate = statusData.last_update || statusData.timestamp || new Date().toISOString();
|
| 482 |
+
|
| 483 |
+
// Format last update time
|
| 484 |
+
let formattedTime = 'N/A';
|
| 485 |
+
try {
|
| 486 |
+
const updateDate = new Date(lastUpdate);
|
| 487 |
+
formattedTime = updateDate.toLocaleString('en-US', {
|
| 488 |
+
year: 'numeric',
|
| 489 |
+
month: '2-digit',
|
| 490 |
+
day: '2-digit',
|
| 491 |
+
hour: '2-digit',
|
| 492 |
+
minute: '2-digit',
|
| 493 |
+
second: '2-digit'
|
| 494 |
+
});
|
| 495 |
+
} catch (e) {
|
| 496 |
+
formattedTime = lastUpdate;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
systemStatusDiv.innerHTML = `
|
| 500 |
+
<div class="alert ${healthClass}">
|
| 501 |
+
<strong>System Status:</strong> ${healthStatus === 'ok' ? 'Healthy' : healthStatus}<br>
|
| 502 |
+
<strong>Total Providers:</strong> ${totalProviders}<br>
|
| 503 |
+
<strong>Online APIs:</strong> ${onlineProviders}<br>
|
| 504 |
+
<strong>Degraded APIs:</strong> ${degradedProviders}<br>
|
| 505 |
+
<strong>Offline APIs:</strong> ${offlineProviders}<br>
|
| 506 |
+
<strong>Avg Response Time:</strong> ${avgResponseTime}ms<br>
|
| 507 |
+
<strong>Last Update:</strong> ${formattedTime}
|
| 508 |
+
</div>
|
| 509 |
+
`;
|
| 510 |
+
}
|
| 511 |
} else {
|
| 512 |
throw new Error('Status endpoint not available');
|
| 513 |
}
|
|
|
|
| 933 |
</div>
|
| 934 |
`;
|
| 935 |
|
| 936 |
+
// Update stat-models in dashboard and sidebar with actual loaded count
|
| 937 |
const modelsStatEl = document.getElementById('stat-models');
|
| 938 |
const sidebarModelsEl = document.getElementById('sidebar-models');
|
| 939 |
+
// Use pipelines_loaded if available, otherwise use modelsCount
|
| 940 |
+
const displayCount = pipelinesLoaded > 0 ? pipelinesLoaded : modelsCount;
|
| 941 |
if (modelsStatEl) {
|
| 942 |
+
modelsStatEl.textContent = displayCount;
|
| 943 |
}
|
| 944 |
if (sidebarModelsEl) {
|
| 945 |
+
sidebarModelsEl.textContent = displayCount;
|
| 946 |
}
|
| 947 |
} else {
|
| 948 |
throw new Error('Models status endpoint not available');
|
|
|
|
| 1019 |
async function initializeModels() {
|
| 1020 |
ToastManager.info('Initializing models... This may take a moment.');
|
| 1021 |
|
| 1022 |
+
const modelsStatusDiv = document.getElementById('models-status');
|
| 1023 |
+
if (modelsStatusDiv) {
|
| 1024 |
+
modelsStatusDiv.innerHTML = '<div class="loading"><div class="spinner"></div> Initializing models...</div>';
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
try {
|
| 1028 |
const response = await fetch('/api/models/initialize', { method: 'POST' });
|
| 1029 |
if (!response.ok) {
|
|
|
|
| 1032 |
|
| 1033 |
const data = await response.json();
|
| 1034 |
|
| 1035 |
+
// Handle different response formats
|
| 1036 |
+
const isOk = data.status === 'ok' || data.ok === true || (data.models_loaded && data.models_loaded > 0);
|
| 1037 |
+
const modelsLoaded = data.models_loaded || data.pipelines_loaded || 0;
|
| 1038 |
+
const modelsFailed = data.models_failed || data.pipelines_failed || 0;
|
| 1039 |
+
|
| 1040 |
+
if (isOk) {
|
| 1041 |
+
ToastManager.success(`Models initialized successfully! ${modelsLoaded} model(s) loaded.`);
|
| 1042 |
+
} else if (modelsLoaded > 0) {
|
| 1043 |
+
ToastManager.warning(`Models partially initialized: ${modelsLoaded} loaded, ${modelsFailed} failed`);
|
| 1044 |
} else {
|
| 1045 |
+
ToastManager.warning('No models loaded. Using fallback mode.');
|
| 1046 |
}
|
| 1047 |
+
|
| 1048 |
+
// Reload models list and status
|
| 1049 |
+
await loadModels();
|
| 1050 |
} catch (error) {
|
| 1051 |
console.error('Error initializing models:', error);
|
| 1052 |
+
ToastManager.error('Failed to initialize models: ' + error.message);
|
| 1053 |
+
|
| 1054 |
+
if (modelsStatusDiv) {
|
| 1055 |
+
modelsStatusDiv.innerHTML = `<div class="alert alert-error">Error initializing models: ${error.message}</div>`;
|
| 1056 |
+
}
|
| 1057 |
}
|
| 1058 |
}
|
| 1059 |
|