// Type declarations for global objects declare global { interface Window { analytics?: { track: (event: string, data: any) => void; }; } } import './style.css'; import { fetchMarketplaceOffers, fetchMarketplaceStats, submitMarketplaceBid, } from './lib/api'; import type { MarketplaceOffer, MarketplaceStats } from './lib/api'; const app = document.querySelector('#app'); if (!app) { throw new Error('Unable to mount marketplace app'); } app.innerHTML = `

Total Offers

-- Listings currently visible

Open Capacity

-- GPU / compute units available

Average Price

-- Credits per unit per hour

Active Bids

-- Open bids awaiting match

Available Offers

Fetching marketplace offers…

Submit a Bid

`; const selectors = { totalOffers: document.querySelector('#stat-total-offers')!, openCapacity: document.querySelector('#stat-open-capacity')!, averagePrice: document.querySelector('#stat-average-price')!, activeBids: document.querySelector('#stat-active-bids')!, statsWrapper: document.querySelector('#stats-grid')!, offersWrapper: document.querySelector('#offers-table-wrapper')!, bidForm: document.querySelector('#bid-form')!, toast: document.querySelector('#toast')!, }; function formatNumber(value: number, options: Intl.NumberFormatOptions = {}): string { return new Intl.NumberFormat(undefined, options).format(value); } function renderStats(stats: MarketplaceStats): void { selectors.totalOffers.textContent = formatNumber(stats.totalOffers); selectors.openCapacity.textContent = `${formatNumber(stats.openCapacity)} units`; selectors.averagePrice.textContent = `${formatNumber(stats.averagePrice, { minimumFractionDigits: 2, maximumFractionDigits: 2, })} credits`; selectors.activeBids.textContent = formatNumber(stats.activeBids); } function statusClass(status: string): string { switch (status.toLowerCase()) { case 'open': return 'status-pill status-open'; case 'reserved': return 'status-pill status-reserved'; default: return 'status-pill'; } } function renderOffers(offers: MarketplaceOffer[]): void { const wrapper = selectors.offersWrapper; if (!wrapper) return; if (offers.length === 0) { wrapper.innerHTML = '

No offers available right now. Check back soon or submit a bid.

'; return; } const cards = offers .map( (offer) => `
${offer.gpu_model || 'Unknown GPU'}
${offer.status}
${offer.provider}${offer.region ? ` · ${offer.region}` : ''}
VRAM ${offer.gpu_memory_gb ? `${offer.gpu_memory_gb} GB` : '—'}
GPUs ${offer.gpu_count ?? 1}×
CUDA ${offer.cuda_version || '—'}
Capacity ${formatNumber(offer.capacity)} units
${offer.attributes?.models?.length ? `
Ollama
Available Models
${offer.attributes.models.map((m: string) => `${m}`).join('')}
` : ''}
${formatNumber(offer.price_per_hour ?? offer.price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} credits/hr
${offer.sla}
`, ) .join(''); wrapper.innerHTML = `
${cards}
`; } function showToast(message: string, duration = 2500): void { if (!selectors.toast) return; selectors.toast.textContent = message; selectors.toast.classList.add('visible'); window.setTimeout(() => { selectors.toast?.classList.remove('visible'); }, duration); } async function loadDashboard(): Promise { // Show skeleton loading states showSkeletons(); try { const [stats, offers] = await Promise.all([ fetchMarketplaceStats(), fetchMarketplaceOffers(), ]); renderStats(stats); renderOffers(offers); } catch (error) { console.error(error); const wrapper = selectors.offersWrapper; if (wrapper) { wrapper.innerHTML = '

Failed to load offers. Please retry shortly.

'; } showToast('Failed to load marketplace data.'); } } function showSkeletons() { const statsWrapper = selectors.statsWrapper; const offersWrapper = selectors.offersWrapper; if (statsWrapper) { statsWrapper.innerHTML = `
${Array(4).fill('').map(() => `
`).join('')}
`; } if (offersWrapper) { offersWrapper.innerHTML = `
${Array(6).fill('').map(() => `
`).join('')}
`; } } selectors.bidForm?.addEventListener('submit', async (event) => { event.preventDefault(); const form = selectors.bidForm; if (!form) return; const formData = new FormData(form); const provider = formData.get('provider')?.toString().trim(); const capacity = Number(formData.get('capacity')); const price = Number(formData.get('price')); const notes = formData.get('notes')?.toString().trim(); if (!provider || Number.isNaN(capacity) || Number.isNaN(price)) { showToast('Please complete the required fields.'); return; } try { const submitButton = form.querySelector('button'); if (submitButton) { submitButton.setAttribute('disabled', 'disabled'); } await submitMarketplaceBid({ provider, capacity, price, notes }); form.reset(); showToast('Bid submitted successfully!'); } catch (error) { console.error(error); showToast('Unable to submit bid. Please try again.'); } finally { const submitButton = form.querySelector('button'); if (submitButton) { submitButton.removeAttribute('disabled'); } } }); loadDashboard();