- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
169 lines
6.7 KiB
Python
169 lines
6.7 KiB
Python
"""
|
|
Cabinet API endpoints for Swapper Service
|
|
Provides data for Node #1 and Node #2 admin consoles
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from typing import Dict, Any, List
|
|
from datetime import datetime
|
|
|
|
# Import will be done after swapper is initialized
|
|
|
|
router = APIRouter(prefix="/api/cabinet", tags=["cabinet"])
|
|
|
|
def get_swapper():
|
|
"""Get swapper instance (lazy import to avoid circular dependency)"""
|
|
from app.main import swapper
|
|
return swapper
|
|
|
|
@router.get("/swapper/status")
|
|
async def get_swapper_status_for_cabinet() -> Dict[str, Any]:
|
|
"""
|
|
Get Swapper Service status for admin console display
|
|
Returns data formatted for Node #1 and Node #2 cabinets
|
|
"""
|
|
try:
|
|
swapper = get_swapper()
|
|
status = await swapper.get_status()
|
|
metrics = await swapper.get_model_metrics()
|
|
|
|
# Format active model info
|
|
active_model_info = None
|
|
if status.active_model:
|
|
active_metrics = next(
|
|
(m for m in metrics if m.model_name == status.active_model),
|
|
None
|
|
)
|
|
if active_metrics:
|
|
active_model_info = {
|
|
"name": status.active_model,
|
|
"uptime_hours": round(active_metrics.uptime_hours, 2),
|
|
"request_count": active_metrics.request_count,
|
|
"loaded_at": active_metrics.loaded_at.isoformat() if active_metrics.loaded_at else None
|
|
}
|
|
|
|
# Format all models with their status
|
|
swapper = get_swapper()
|
|
models_info = []
|
|
for model_name in status.available_models:
|
|
model_metrics = next(
|
|
(m for m in metrics if m.model_name == model_name),
|
|
None
|
|
)
|
|
model_data = swapper.models.get(model_name)
|
|
|
|
if model_data:
|
|
models_info.append({
|
|
"name": model_name,
|
|
"ollama_name": model_data.ollama_name,
|
|
"type": model_data.type,
|
|
"size_gb": model_data.size_gb,
|
|
"priority": model_data.priority,
|
|
"status": model_data.status.value,
|
|
"is_active": model_name == status.active_model,
|
|
"uptime_hours": round(model_metrics.uptime_hours, 2) if model_metrics else 0.0,
|
|
"request_count": model_metrics.request_count if model_metrics else 0,
|
|
"total_uptime_seconds": model_metrics.total_uptime_seconds if model_metrics else 0.0
|
|
})
|
|
|
|
return {
|
|
"service": "swapper-service",
|
|
"status": status.status,
|
|
"mode": status.mode,
|
|
"active_model": active_model_info,
|
|
"total_models": status.total_models,
|
|
"available_models": status.available_models,
|
|
"loaded_models": status.loaded_models,
|
|
"models": models_info,
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error getting Swapper status: {str(e)}")
|
|
|
|
@router.get("/swapper/models")
|
|
async def get_swapper_models_for_cabinet() -> Dict[str, Any]:
|
|
"""
|
|
Get all models with detailed information for cabinet display
|
|
"""
|
|
try:
|
|
swapper = get_swapper()
|
|
status = await swapper.get_status()
|
|
metrics = await swapper.get_model_metrics()
|
|
|
|
models_detail = []
|
|
for model_name in status.available_models:
|
|
model_data = swapper.models.get(model_name)
|
|
model_metrics = next(
|
|
(m for m in metrics if m.model_name == model_name),
|
|
None
|
|
)
|
|
|
|
if model_data:
|
|
models_detail.append({
|
|
"name": model_name,
|
|
"ollama_name": model_data.ollama_name,
|
|
"type": model_data.type,
|
|
"size_gb": model_data.size_gb,
|
|
"priority": model_data.priority,
|
|
"status": model_data.status.value,
|
|
"is_active": model_name == status.active_model,
|
|
"can_load": model_data.status.value in ["unloaded", "error"],
|
|
"can_unload": model_data.status.value == "loaded",
|
|
"uptime_hours": round(model_metrics.uptime_hours, 2) if model_metrics else 0.0,
|
|
"request_count": model_metrics.request_count if model_metrics else 0,
|
|
"total_uptime_seconds": model_metrics.total_uptime_seconds if model_metrics else 0.0,
|
|
"loaded_at": model_metrics.loaded_at.isoformat() if model_metrics and model_metrics.loaded_at else None
|
|
})
|
|
|
|
return {
|
|
"models": models_detail,
|
|
"total": len(models_detail),
|
|
"active_count": len(status.loaded_models),
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error getting models: {str(e)}")
|
|
|
|
@router.get("/swapper/metrics/summary")
|
|
async def get_swapper_metrics_summary() -> Dict[str, Any]:
|
|
"""
|
|
Get summary metrics for cabinet dashboard
|
|
"""
|
|
try:
|
|
swapper = get_swapper()
|
|
status = await swapper.get_status()
|
|
metrics = await swapper.get_model_metrics()
|
|
|
|
# Calculate totals
|
|
total_uptime_hours = sum(m.uptime_hours for m in metrics)
|
|
total_requests = sum(m.request_count for m in metrics)
|
|
|
|
# Most used model
|
|
most_used = max(metrics, key=lambda m: m.total_uptime_seconds) if metrics else None
|
|
|
|
return {
|
|
"summary": {
|
|
"total_models": status.total_models,
|
|
"active_models": len(status.loaded_models),
|
|
"available_models": len(status.available_models),
|
|
"total_uptime_hours": round(total_uptime_hours, 2),
|
|
"total_requests": total_requests
|
|
},
|
|
"most_used_model": {
|
|
"name": most_used.model_name,
|
|
"uptime_hours": round(most_used.uptime_hours, 2),
|
|
"request_count": most_used.request_count
|
|
} if most_used else None,
|
|
"active_model": {
|
|
"name": status.active_model,
|
|
"uptime_hours": round(
|
|
next((m.uptime_hours for m in metrics if m.model_name == status.active_model), 0.0),
|
|
2
|
|
) if status.active_model else None
|
|
} if status.active_model else None,
|
|
"timestamp": datetime.now().isoformat()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error getting metrics summary: {str(e)}")
|
|
|