Spaces:
Running
Running
| const API_BASE = import.meta.env.VITE_API_BASE ?? ''; | |
| const REFRESH_MS = 3000; | |
| const SAMPLE_IMAGE_URL = | |
| 'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png'; | |
| interface HostInfo { | |
| id: string; | |
| model: string; | |
| online: boolean; | |
| registeredAt: number; | |
| lastHeartbeat: number; | |
| } | |
| const brokerStatusEl = document.getElementById('broker-status')!; | |
| const onlineCountEl = document.getElementById('online-count')!; | |
| const totalCountEl = document.getElementById('total-count')!; | |
| const hostsEl = document.getElementById('hosts')!; | |
| const errorEl = document.getElementById('error')!; | |
| const refreshNoteEl = document.getElementById('refresh-note')!; | |
| const expandedCurlHosts = new Set<string>(); | |
| function syncExpandedCurlFromDom(): void { | |
| hostsEl.querySelectorAll<HTMLDetailsElement>('details.curl-details').forEach((details) => { | |
| const id = details.closest('[data-host-id]')?.getAttribute('data-host-id'); | |
| if (!id) { | |
| return; | |
| } | |
| if (details.open) { | |
| expandedCurlHosts.add(id); | |
| } else { | |
| expandedCurlHosts.delete(id); | |
| } | |
| }); | |
| } | |
| function detectOrigin(): string { | |
| return window.location.origin; | |
| } | |
| function buildCurlExample(hostId: string, modelId: string): string { | |
| const origin = detectOrigin(); | |
| if (modelId === 'smolvlm-500m') { | |
| return `curl -X POST '${origin}/v1/describe' \\ | |
| -H 'Content-Type: application/json' \\ | |
| -d '{ | |
| "host": "${hostId}", | |
| "image_url": "${SAMPLE_IMAGE_URL}", | |
| "instruction": "What do you see?", | |
| "max_new_tokens": 100 | |
| }'`; | |
| } | |
| return `curl -X POST '${origin}/v1/detect' \\ | |
| -H 'Content-Type: application/json' \\ | |
| -d '{ | |
| "host": "${hostId}", | |
| "image_url": "${SAMPLE_IMAGE_URL}", | |
| "threshold": 0.5 | |
| }'`; | |
| } | |
| function formatTime(ms: number): string { | |
| return new Date(ms).toLocaleString(); | |
| } | |
| function relativeTime(ms: number): string { | |
| const seconds = Math.floor((Date.now() - ms) / 1000); | |
| if (seconds < 60) { | |
| return `${seconds}s ago`; | |
| } | |
| const minutes = Math.floor(seconds / 60); | |
| if (minutes < 60) { | |
| return `${minutes}m ago`; | |
| } | |
| const hours = Math.floor(minutes / 60); | |
| return `${hours}h ago`; | |
| } | |
| function showError(message: string): void { | |
| errorEl.textContent = message; | |
| errorEl.style.display = 'block'; | |
| } | |
| function clearError(): void { | |
| errorEl.style.display = 'none'; | |
| } | |
| async function fetchBrokerHealth(): Promise<boolean> { | |
| const res = await fetch(`${API_BASE}/health`); | |
| if (!res.ok) { | |
| return false; | |
| } | |
| const json = (await res.json()) as {status?: string}; | |
| return json.status === 'ok'; | |
| } | |
| async function fetchHosts(): Promise<HostInfo[]> { | |
| const res = await fetch(`${API_BASE}/v1/hosts`); | |
| if (!res.ok) { | |
| throw new Error(`GET /v1/hosts failed (${res.status})`); | |
| } | |
| const json = (await res.json()) as {hosts: HostInfo[]}; | |
| return json.hosts ?? []; | |
| } | |
| function renderHosts(hosts: HostInfo[]): void { | |
| const online = hosts.filter((h) => h.online).length; | |
| onlineCountEl.textContent = String(online); | |
| onlineCountEl.className = `stat-value ${online > 0 ? 'ok' : 'warn'}`; | |
| totalCountEl.textContent = String(hosts.length); | |
| if (hosts.length === 0) { | |
| hostsEl.innerHTML = ` | |
| <div class="empty"> | |
| <p>No hosts registered yet.</p> | |
| <p><a href="/public">Open the host page</a>, pick an id, and click “Start hosting”.</p> | |
| </div>`; | |
| return; | |
| } | |
| const sorted = [...hosts].sort((a, b) => { | |
| if (a.online !== b.online) { | |
| return a.online ? -1 : 1; | |
| } | |
| return b.lastHeartbeat - a.lastHeartbeat; | |
| }); | |
| syncExpandedCurlFromDom(); | |
| hostsEl.innerHTML = sorted | |
| .map((host) => { | |
| const curl = buildCurlExample(host.id, host.model); | |
| const escapedCurl = curl.replace(/'/g, '''); | |
| return ` | |
| <article class="host-card" data-host-id="${escapeHtml(host.id)}"> | |
| <div class="host-header"> | |
| <span class="host-id">${escapeHtml(host.id)}</span> | |
| <span class="badge ${host.online ? 'online' : 'offline'}"> | |
| ${host.online ? 'Online' : 'Offline'} | |
| </span> | |
| </div> | |
| <div class="host-meta"> | |
| <span>Model: ${escapeHtml(host.model)}</span> | |
| <span>Registered: ${formatTime(host.registeredAt)}</span> | |
| <span>Last heartbeat: ${relativeTime(host.lastHeartbeat)}</span> | |
| </div> | |
| <details class="curl-details"${expandedCurlHosts.has(host.id) ? ' open' : ''}> | |
| <summary> | |
| <span class="summary-show">Show curl example</span> | |
| <span class="summary-hide">Hide curl example</span> | |
| </summary> | |
| <div class="curl-body"> | |
| <button type="button" class="copy" data-curl="${escapedCurl}">Copy curl</button> | |
| <pre>${escapeHtml(curl)}</pre> | |
| </div> | |
| </details> | |
| </article>`; | |
| }) | |
| .join(''); | |
| hostsEl.querySelectorAll('button.copy').forEach((btn) => { | |
| btn.addEventListener('click', async () => { | |
| const curl = btn.getAttribute('data-curl')?.replace(/'/g, "'") ?? ''; | |
| await navigator.clipboard.writeText(curl); | |
| const label = btn.textContent; | |
| btn.textContent = 'Copied!'; | |
| setTimeout(() => { | |
| btn.textContent = label; | |
| }, 1500); | |
| }); | |
| }); | |
| } | |
| function escapeHtml(text: string): string { | |
| return text | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"'); | |
| } | |
| async function refresh(): Promise<void> { | |
| try { | |
| const healthy = await fetchBrokerHealth(); | |
| brokerStatusEl.textContent = healthy ? 'Healthy' : 'Unreachable'; | |
| brokerStatusEl.className = `stat-value ${healthy ? 'ok' : 'err'}`; | |
| if (!healthy) { | |
| showError( | |
| `Broker at ${detectOrigin()} is not responding. Run \`npm run dev\` or \`npm run dev:api\`.`, | |
| ); | |
| hostsEl.innerHTML = ''; | |
| onlineCountEl.textContent = '—'; | |
| totalCountEl.textContent = '—'; | |
| return; | |
| } | |
| clearError(); | |
| const hosts = await fetchHosts(); | |
| renderHosts(hosts); | |
| refreshNoteEl.textContent = `Last updated ${new Date().toLocaleTimeString()} · refreshing every ${REFRESH_MS / 1000}s`; | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error); | |
| showError(message); | |
| brokerStatusEl.textContent = 'Error'; | |
| brokerStatusEl.className = 'stat-value err'; | |
| } | |
| } | |
| hostsEl.addEventListener('toggle', (event) => { | |
| const details = event.target; | |
| if (!(details instanceof HTMLDetailsElement) || !details.classList.contains('curl-details')) { | |
| return; | |
| } | |
| const id = details.closest('[data-host-id]')?.getAttribute('data-host-id'); | |
| if (!id) { | |
| return; | |
| } | |
| if (details.open) { | |
| expandedCurlHosts.add(id); | |
| } else { | |
| expandedCurlHosts.delete(id); | |
| } | |
| }); | |
| void refresh(); | |
| setInterval(() => { | |
| void refresh(); | |
| }, REFRESH_MS); | |