Market and providers show now gpu for coderai provs

parent c0b75758
......@@ -121,9 +121,15 @@ async def _coderai_broker_websocket_impl(websocket: WebSocket, scope_name: str):
registered_payload = _coderai_register_payload(session.provider_id, session.client_id, username, owner_user_id, expected_scope)
registered_payload.update({"session_id": session.session_id, "status": "ok", "request_id": first_msg.get("request_id")})
await websocket.send_text(json.dumps(registered_payload))
_gpu_names = ", ".join(
g.get("name", "?") for g in (metadata.get("gpus") or [])
) or "none"
logger.info(
"CoderAI broker registered provider=%s client=%s session_id=%s scope=%s",
"CoderAI broker registered provider=%s client=%s session_id=%s scope=%s"
" gpu_count=%s gpus=[%s] total_vram_mb=%s available_vram_mb=%s",
provider_id, client_id, session.session_id, expected_scope,
metadata.get("gpu_count"), _gpu_names,
metadata.get("total_vram_mb"), metadata.get("available_vram_mb"),
)
# Populate the model cache in the background now that the session is live.
......
......@@ -3,6 +3,7 @@ from fastapi.responses import HTMLResponse, JSONResponse
from aisbf.routes.auth import require_dashboard_auth, require_api_auth, require_admin
from aisbf.database import DatabaseRegistry
from aisbf.analytics import get_analytics
from aisbf.coderai_broker import broker as coderai_broker
import json
import logging
......@@ -184,6 +185,29 @@ def _build_autoselect_listing_payload(owner_user_id: int, owner_username: str, a
}
async def _attach_hardware_snapshot(listing: dict) -> dict:
"""Attach live broker hardware metadata for coderai listings."""
metadata = listing.get('metadata') or {}
if metadata.get('provider_type') != 'coderai':
return listing
provider_id = listing.get('provider_id')
if not provider_id:
return listing
try:
snapshot = await coderai_broker.get_session_snapshot(provider_id)
if snapshot and snapshot.get('connected'):
smeta = snapshot.get('metadata') or {}
listing['hardware'] = {
'gpu_count': smeta.get('gpu_count', 0),
'gpus': smeta.get('gpus') or [],
'total_vram_mb': smeta.get('total_vram_mb'),
'available_vram_mb': smeta.get('available_vram_mb'),
}
except Exception:
pass
return listing
def _attach_analytics_snapshot(listing: dict):
analytics = get_analytics()
source_type = listing.get('source_type')
......@@ -378,6 +402,7 @@ async def dashboard_market(request: Request):
listing_copy['online'] = _listing_online(listing_copy)
owner = db.get_user_by_id(listing['owner_user_id']) if listing.get('owner_user_id') else None
listing_copy['owner_display_name'] = (owner or {}).get('display_name') or listing.get('owner_username')
await _attach_hardware_snapshot(listing_copy)
enriched.append(listing_copy)
return _templates.TemplateResponse(
......@@ -477,6 +502,7 @@ async def api_market_listings(request: Request):
for listing in filtered:
listing.pop('_scope_priority', None)
listing.pop('_match_priority', None)
await _attach_hardware_snapshot(listing)
return JSONResponse({'listings': filtered})
......
......@@ -66,6 +66,30 @@ function voteSummary(votes, target) {
return `+${item.upvotes} / -${item.downvotes} (score ${item.score})`;
}
function formatVramMb(value) {
if (value == null || value === '') return '?';
const mb = Number(value);
if (!Number.isFinite(mb)) return '?';
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
return `${Math.round(mb)} MB`;
}
function renderListingHardware(hw) {
if (!hw) return '';
const gpus = Array.isArray(hw.gpus) ? hw.gpus : [];
const gpuName = gpus[0]?.name || null;
const total = hw.total_vram_mb;
const free = hw.available_vram_mb;
if (!gpuName && total == null) return '';
const parts = [];
if (gpuName) {
const short = gpuName.replace(/^NVIDIA\s+(GeForce\s+)?/i, '').replace(/^AMD\s+(Radeon\s+RX\s+)?/i, '');
parts.push(short);
}
if (total != null) parts.push(`${formatVramMb(free)} / ${formatVramMb(total)} VRAM`);
return `<div style="font-size:12px;margin-bottom:8px;display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:999px;background:rgba(99,102,241,0.1);border:1px solid rgba(99,102,241,0.3);color:#a5b4fc;">${parts.join(' · ')}</div>`;
}
function renderMarket() {
const root = document.getElementById('market-grid');
root.innerHTML = '';
......@@ -86,6 +110,7 @@ function renderMarket() {
<p style="color: var(--color-text); margin: 12px 0;">${listing.description || ''}</p>
<div style="font-size:12px; color: var(--color-muted); margin-bottom: 10px;">Provider: ${listing.provider_id || '-'} ${listing.model_id ? ` | Model: ${listing.model_id}` : ''} ${listing.model_type_label ? ` | Type: ${listing.model_type_label}` : ''} ${listing.size_label ? ` | Size: ${listing.size_label}` : ''}</div>
<div style="font-size:12px; color: var(--color-muted); margin-bottom: 10px;">Status: ${listing.online ? 'online' : 'offline'}${(listing.capabilities || []).length ? ` | Capabilities: ${(listing.capabilities || []).join(', ')}` : ''}</div>
${renderListingHardware(listing.hardware)}
<div style="display:grid; grid-template-columns:1fr 1fr; gap:6px; font-size:12px; margin-bottom:12px;">
<div>Listing votes: ${voteSummary(listing.votes, 'listing')}</div>
<div>User votes: ${voteSummary(listing.votes, 'user')}</div>
......
......@@ -200,7 +200,26 @@ function renderCoderaiCollapsedStatus(provider) {
const bg = connected ? 'rgba(34,197,94,0.16)' : 'rgba(239,68,68,0.16)';
const border = connected ? 'rgba(34,197,94,0.45)' : 'rgba(239,68,68,0.45)';
const color = connected ? '#22c55e' : '#ef4444';
return `<span style="display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600;background:${bg};border:1px solid ${border};color:${color};"><span style="width:8px;height:8px;border-radius:50%;background:${color};display:inline-block;"></span>${label}</span>`;
const statusBadge = `<span style="display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600;background:${bg};border:1px solid ${border};color:${color};"><span style="width:8px;height:8px;border-radius:50%;background:${color};display:inline-block;"></span>${label}</span>`;
if (!connected) return statusBadge;
const meta = coderaiConfig.broker_session?.metadata || {};
return statusBadge + renderCoderaiHardwareChip(meta);
}
function renderCoderaiHardwareChip(meta) {
const gpus = Array.isArray(meta?.gpus) ? meta.gpus : [];
const gpuName = gpus[0]?.name || null;
const totalVram = meta?.total_vram_mb;
const freeVram = meta?.available_vram_mb;
if (!gpuName && totalVram == null) return '';
const parts = [];
if (gpuName) {
const shortName = gpuName.replace(/^NVIDIA\s+(GeForce\s+)?/i, '').replace(/^AMD\s+(Radeon\s+RX\s+)?/i, '');
parts.push(shortName);
}
if (totalVram != null) parts.push(`${formatVramMb(freeVram)} / ${formatVramMb(totalVram)}`);
if (!parts.length) return '';
return `<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:999px;font-size:12px;background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.35);color:#a5b4fc;margin-left:4px;">${parts.join(' · ')}</span>`;
}
function formatRunpodTimestamp(value) {
......
......@@ -196,7 +196,26 @@ function renderCoderaiCollapsedStatus(provider) {
const bg = connected ? 'rgba(34,197,94,0.16)' : 'rgba(239,68,68,0.16)';
const border = connected ? 'rgba(34,197,94,0.45)' : 'rgba(239,68,68,0.45)';
const color = connected ? '#22c55e' : '#ef4444';
return `<span style="display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600;background:${bg};border:1px solid ${border};color:${color};"><span style="width:8px;height:8px;border-radius:50%;background:${color};display:inline-block;"></span>${label}</span>`;
const statusBadge = `<span style="display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600;background:${bg};border:1px solid ${border};color:${color};"><span style="width:8px;height:8px;border-radius:50%;background:${color};display:inline-block;"></span>${label}</span>`;
if (!connected) return statusBadge;
const meta = coderaiConfig.broker_session?.metadata || {};
return statusBadge + renderCoderaiHardwareChip(meta);
}
function renderCoderaiHardwareChip(meta) {
const gpus = Array.isArray(meta?.gpus) ? meta.gpus : [];
const gpuName = gpus[0]?.name || null;
const totalVram = meta?.total_vram_mb;
const freeVram = meta?.available_vram_mb;
if (!gpuName && totalVram == null) return '';
const parts = [];
if (gpuName) {
const shortName = gpuName.replace(/^NVIDIA\s+(GeForce\s+)?/i, '').replace(/^AMD\s+(Radeon\s+RX\s+)?/i, '');
parts.push(shortName);
}
if (totalVram != null) parts.push(`${formatVramMb(freeVram)} / ${formatVramMb(totalVram)}`);
if (!parts.length) return '';
return `<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:999px;font-size:12px;background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.35);color:#a5b4fc;margin-left:4px;">${parts.join(' · ')}</span>`;
}
function renderGpuSummary(metadata) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment