import { CLUSTER_MODELS, DEFAULT_CLUSTER_MODEL_ID, getClusterModel, isImplementedClusterModel, OBJECT_DETECTION_MODEL_ID, VIDEO_DESCRIPTION_MODEL_ID, } from '../../shared/clusterModels'; import { ClusterTaskHandler, type ClusterTaskKind, type HostTaskMessage, } from '../clusterTaskHandler'; import type {ObjectDetector} from '../detection/ObjectDetector'; import type {VideoFrameDescriber} from '../videodescription/VideoFrameDescriber'; const API_BASE = import.meta.env.VITE_API_BASE ?? ''; const statusEl = document.getElementById('status')!; const hostIdInput = document.getElementById('host-id') as HTMLInputElement; const modelSelect = document.getElementById('model-select') as HTMLSelectElement; const registerBtn = document.getElementById('register-btn') as HTMLButtonElement; const curlExample = document.getElementById('curl-example')!; let detector: ObjectDetector | null = null; let describer: VideoFrameDescriber | null = null; let hostId = ''; let selectedModelId = DEFAULT_CLUSTER_MODEL_ID; let eventSource: EventSource | null = null; const taskHandler = new ClusterTaskHandler({ apiBase: API_BASE, getSelectedModelId: () => selectedModelId, getDetector: () => detector, getDescriber: () => describer, setStatus, }); function setStatus(message: string): void { statusEl.textContent = message; console.log('[detection-host]', message); } function populateModelSelect(): void { modelSelect.replaceChildren( ...CLUSTER_MODELS.map((model) => { const option = document.createElement('option'); option.value = model.id; option.textContent = model.implemented ? model.label : `${model.label} (coming soon)`; option.disabled = !model.implemented; option.title = model.description; return option; }), ); modelSelect.value = DEFAULT_CLUSTER_MODEL_ID; } function updateCurlExample(): void { const origin = window.location.origin; if (selectedModelId === OBJECT_DETECTION_MODEL_ID) { curlExample.textContent = `curl -X POST '${origin}/v1/detect' \\ -H 'Content-Type: application/json' \\ -d '{ "host": "${hostId}", "image_url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png", "threshold": 0.5 }'`; return; } if (selectedModelId === VIDEO_DESCRIPTION_MODEL_ID) { curlExample.textContent = `curl -X POST '${origin}/v1/describe' \\ -H 'Content-Type: application/json' \\ -d '{ "host": "${hostId}", "image_url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png", "instruction": "What do you see?", "max_new_tokens": 100 }'`; return; } const model = getClusterModel(selectedModelId); curlExample.textContent = model ? `# ${model.label}\n# API endpoint for this model is not wired yet.` : ''; } async function ensureModelLoaded(modelId: string): Promise { if (modelId === OBJECT_DETECTION_MODEL_ID) { if (!detector) { setStatus('Loading RF-DETR (WebGPU)…'); const {ObjectDetector: Detector} = await import('../detection/ObjectDetector'); detector = await Detector.create(setStatus); } return; } if (modelId === VIDEO_DESCRIPTION_MODEL_ID) { if (!describer) { setStatus('Loading SmolVLM-500M (WebGPU)…'); const {VideoFrameDescriber: Describer} = await import('../videodescription/VideoFrameDescriber'); describer = await Describer.create(setStatus); } return; } const model = getClusterModel(modelId); throw new Error( model ? `${model.label} is not available on this host yet.` : `Unknown model "${modelId}".`, ); } function connectStream(): void { eventSource?.close(); const url = `${API_BASE}/api/hosts/stream?host_id=${encodeURIComponent(hostId)}`; eventSource = new EventSource(url); eventSource.addEventListener('ready', () => { const model = getClusterModel(selectedModelId); const modelLabel = model?.label ?? selectedModelId; setStatus(`Hosting "${hostId}" (${modelLabel}). Waiting for requests…`); }); eventSource.addEventListener('task', (event) => { const raw = JSON.parse(event.data) as HostTaskMessage & {kind?: ClusterTaskKind}; const defaultKind: ClusterTaskKind = selectedModelId === VIDEO_DESCRIPTION_MODEL_ID ? 'describe' : 'detect'; taskHandler.enqueueTask(taskHandler.parseIncomingTask(raw, defaultKind)); }); eventSource.onerror = () => { if (eventSource?.readyState === EventSource.CLOSED) { setStatus('Broker connection lost. Reconnecting in 2s…'); setTimeout(connectStream, 2000); } }; } async function registerHost(): Promise { hostId = hostIdInput.value.trim(); selectedModelId = modelSelect.value; if (!hostId) { setStatus('Enter a host id (e.g. my-gpu-node).'); return; } if (!isImplementedClusterModel(selectedModelId)) { setStatus('Choose an available model from the list.'); return; } registerBtn.disabled = true; modelSelect.disabled = true; hostIdInput.disabled = true; try { await ensureModelLoaded(selectedModelId); const res = await fetch(`${API_BASE}/api/hosts/register`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({id: hostId, model: selectedModelId}), }); if (!res.ok) { const json = await res.json().catch(() => ({})); throw new Error(json.error ?? `Register failed (${res.status})`); } updateCurlExample(); connectStream(); } catch (error) { const message = error instanceof Error ? error.message : String(error); setStatus(`Failed to start host:\n${message}`); registerBtn.disabled = false; modelSelect.disabled = false; hostIdInput.disabled = false; } } registerBtn.addEventListener('click', () => { void registerHost(); }); modelSelect.addEventListener('change', () => { selectedModelId = modelSelect.value; updateCurlExample(); }); populateModelSelect(); hostIdInput.value = `node-${Math.random().toString(36).slice(2, 8)}`; selectedModelId = modelSelect.value; updateCurlExample(); setStatus('Pick a model, enter a host id, and click “Start hosting”.');