feat: Add presence heartbeat for Matrix online status
- 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
This commit is contained in:
168
services/swapper-service/app/cabinet_api.py
Normal file
168
services/swapper-service/app/cabinet_api.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
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)}")
|
||||
|
||||
Reference in New Issue
Block a user